zoukankan      html  css  js  c++  java
  • 单例模式也能玩出花

    一、单例模式

    1、什么是单例模式

    (1)单例模式

    【单例模式(Singleton Pattern):】
    定义:
        Ensure a class has only one instance, and provide a global point of access to it.
        直译:确保一个类只有一个实例,并提供对它的全局访问点(只允许通过全局访问点获取实例对象)。

    (2)单例模式实现要点

    一般情况下,访问类中某变量、方法:
        可以通过 new 进行对象实例化,再通过 "对象名.变量名"、"对象名.方法名" 的形式获取。
        可以通过 static 修饰(全局)变量、方法,再通过 "类名.变量名"、"类名.方法名" 的形式获取。可避免使用 new 进行对象实例化。
    
    为了保证一个类只存在一个实例,应该保证其有且只有 一次 实例化 机会:
        应该保证其构造方法不能在该类以外的地方被调用(防止使用 new 进行对象实例化)。
        构造方法只能在该类中被调用一次。
    注:
        对象实例化常见方式: new、序列化、克隆、反射。
        
    基本实现要点:
        构造方法私有化(防止使用 new 进行对象实例化)。
        在类的内部进行一次实例化(构造方法只能在该类中被调用一次)。
        对外提供一个全局访问点(全局变量、全局方法等),可以通过 "类名.变量名" 或者 "类名.方法名" 的形式获取实例对象(避免使用 new 进行对象实例化)。
    注:
        反射会破坏 构造方法的私有化,需要注意,后面会介绍。
        序列化、克隆 等操作可能会破坏单例模式。需要注意。

    (3)使用场景
      当频繁创建、销毁某个对象时,可以考虑单例模式。
      当创建对象消耗资源过多时,但又经常使用时,可以考虑单例模式。

    2、常见单例模式实现方式

    (1)实现方式

    【饿汉式:】
        静态变量
        静态代码块
        枚举(推荐)
    
    【懒汉式:】
        静态方法
        synchronized 同步方法
        synchronized 同步代码块
        双重检查
        静态内部类

    (2)饿汉式、懒汉式 区别

    【基本区别:】
        懒汉式 在需要使用对象的时候才进行实例化操作。
        饿汉式 在类加载时完成实例化操作,可能暂时还不用该对象(占用内存)。
    
    【饿汉式:】
    核心:
        饿汉式借助 JVM 的类加载机制,在 类加载的初始化阶段 完成 实例化操作。
        类初始化阶段 只会执行一次,从而保证实例的唯一性 以及 线程安全。
        当类被主动使用时,才会导致类的初始化。而被动使用时,不会导致类的初始化。
    主动使用类的方式:
        类的 main 方法被调用时。
        执行 new 实例化操作时。
        访问静态变量、静态方法时。
        实例化子类时(先触发父类初始化)。
        反射调用某类时。
    JVM 类加载过程可参考: https://www.cnblogs.com/l-y-h/p/13496969.html#_label1_5
    
    【懒汉式:】
    核心:
        在需要使用对象的时候才进行实例化操作。
        多线程环境下,多个线程可能同时使用对象,需要考虑线程安全问题,防止并发访问生成多个实例。

    二、饿汉式

    1、实现

    (1)基本说明

    【核心思路:】
        使用 static 关键字,借助类加载过程,进行实例的初始化。
        使用 private 修饰 构造方法,保证构造方法私有化。
        提供一个全局访问点(类名.变量名 或者 类名.方法名)获取对象。    
     
    【可用方式:】
        静态变量
        静态方法
        静态代码块
    
    【优点:】
        在类加载的初始化阶段完成了实例化,仅加载一次。保证对象的唯一性 以及 线程安全。
        
    【缺点:】
        在类加载的初始化阶段完成了实例化,没有实现懒加载(Lazy Loading),可能造成内存的浪费(在不需要使用的时候被创建)。

    (2)代码实现(静态变量)
      public 修饰变量,直接通过 "类名.变量名" 的方式获取对象。

    class HungrySingleton {
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton singleton = new HungrySingleton();
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            HungrySingleton singleton = HungrySingleton.singleton;
            HungrySingleton singleton2 = HungrySingleton.singleton;
            System.out.println(singleton == singleton2);  // true,为同一个对象
        }
    }

    (3)代码实现(静态方法)
      private 修饰变量,不允许通过 "类名.变量名" 的形式访问。
      public 修饰方法,通过 "类名.方法名" 的方式获取对象。

    class HungrySingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton = new HungrySingleton();
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            HungrySingleton singleton = HungrySingleton.getInstance();
            HungrySingleton singleton2 = HungrySingleton.getInstance();
            System.out.println(singleton == singleton2);  // true,为同一个对象
        }
    }

    (4)代码实现(静态代码块)
      静态代码块,只是将实例化操作 移动到 静态代码块中进行实现。

    class HungrySingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton;
    
        // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
        static {
            singleton = new HungrySingleton();
        }
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            HungrySingleton singleton = HungrySingleton.getInstance();
            HungrySingleton singleton2 = HungrySingleton.getInstance();
            System.out.println(singleton == singleton2);  // true,为同一个对象
        }
    }

    (5)这就完了吗?
      当然不是了,这样写只是防止了通过 new 实例化对象。
      对象实例化的方式还有 反射、序列化、克隆 等操作。
      这些操作是否会破坏单例模式?需要思考一下。

    2、反射破坏

    (1)类主动使用时,才会进行类的初始化
      类只有主动使用时,才会进行初始化操作。并不一定使用到类,就会触发初始化操作。
    比如:
      进行反射获取私有构造方法时,并不会触发 类加载过程。
      如下代码执行后,静态代码块中的 "start..." 不会输出。

    import java.lang.reflect.Constructor;
    
    class HungrySingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton;
    
        // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
        static {
            System.out.println("start...");
            singleton = new HungrySingleton();
        }
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
            Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
            hungrySingletonConstructor.setAccessible(true);
        }
    }

    (2)反射破坏
      如下代码所示,反射调用构造方法时,会进行类加载过程(输出 "start..." ),然后构建一个实例。
      此时的实例对象是通过 构造方法重新创建的对象。与类加载过程中创建的对象不同。
      即 反射对 单例模式造成了破坏。

    import java.lang.reflect.Constructor;
    
    class HungrySingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton;
    
        // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
        static {
            System.out.println("start...");
            singleton = new HungrySingleton();
        }
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
            Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
            hungrySingletonConstructor.setAccessible(true); // 此时,还不会触发 类加载过程
            HungrySingleton singleton = hungrySingletonConstructor.newInstance(); // 此时,触发 类加载过程,并创建一个实例
            HungrySingleton singleton2 = HungrySingleton.getInstance();
            System.out.println(singleton == singleton2);  // false,不为同一个对象
         }
    }

    (3)防止反射破坏(未必会生效)
      在构造方法中,判断实例是否已经被创建。
      类初始化过程中,会创建一个实例。即使通过反射调用构造方法,也会在实例创建之后再去调用,所以在 构造方法中进行判断,实例存在则会抛出异常。从而防止反射破坏(未必会生效,后续序列化破坏中有提到)。

    import java.lang.reflect.Constructor;
    
    class HungrySingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton;
    
        // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
        static {
            System.out.println("start...");
            singleton = new HungrySingleton();
        }
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
            if (singleton != null) {
                throw new RuntimeException("实例已存在,不允许重复创建");
            }
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
            Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
            hungrySingletonConstructor.setAccessible(true);
            HungrySingleton singleton = hungrySingletonConstructor.newInstance();
            HungrySingleton singleton2 = HungrySingleton.getInstance();
            System.out.println(singleton == singleton2);  // true,为同一个对象
        }
    }

    3、反序列化破坏

    (1)序列化、反序列化
      序列化对象,并再次读取对象时(反序列化),会创建一个新的对象。
    注:
      序列化就是把实体对象状态按照一定的格式写入到有序字节流。
      反序列化就是从有序字节流重建对象,恢复对象状态。

    【反序列化核心代码:】
    ObjectInputStream 中的 readOrdinaryObject() 方法
    
    private Object readOrdinaryObject(boolean unshared) throws IOException {
        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(desc.forClass().getName(), "unable to create instance").initCause(ex);
        }
    
        if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
            Object rep = desc.invokeReadResolve(obj);
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }
        return obj;
    }
    
    【关注点一:(调用构造函数实例化)】
    desc.isInstantiable()
        如果一个 serializable/externalizable 的类可以在运行时被实例化,那么该方法就返回true。
    
    desc.newInstance()
        通过反射调用无参构造创建一个对象。
    注:
        此处调用的无参构造,与类本身的无参构造方法有差别。
        从实际效果上看,此处仅触发了类加载,并未触发类的构造函数。与前面提到的反射有区别。
        没有深入研究,有兴趣的可以帮忙解答一下。
    
    【关注点二:(自定义对象生成策略)】
    desc.hasReadResolveMethod()
        如果一个 serializable/externalizable 接口的类中包含 readResolve() 方法,则返回 true。
    
    desc.invokeReadResolve(obj)
        通过反射的方式调用要被反序列化的类的 readResolve() 方法。
    
    handles.setObject(passHandle, obj = rep)
        如果 readResolve() 返回的实例与构造方法创建的不同,则以 readResolve() 方法创建的实例为准。

    (2)反序列化破坏
      如下代码所示,通过反序列化创建了个对象。
      从实际代码执行结果看,反序列化仅触发了类加载过程(此时调用了构造函数),反序列化中 newInstance() 未主动触发类的构造函数,所以此处构造方法中的判断 无法防止 反序列化中反射的行为。
      即 反序列化对 单例模式造成了破坏。

    import java.io.FileInputStream;
    import java.io.ObjectInputStream;
    import java.io.Serializable;
    
    class HungrySingleton implements Serializable {
        private static final long serialVersionUID = 42L;
    
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton;
    
        static {
            System.out.println("start...");  // start...
            singleton = new HungrySingleton();
        }
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
            if (singleton != null) {
                throw new RuntimeException("实例已存在,不允许重复创建");
            }
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
    //        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
    //        oos.writeObject(HungrySingleton.getInstance());
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
            HungrySingleton singleton = (HungrySingleton) ois.readObject();
    
            HungrySingleton singleton2 = HungrySingleton.getInstance();
            System.out.println(singleton == singleton2); // false,不为同一对象
        }
    }

    (3)防止反序列化破坏
      通过 readResolve() 可以返回一个实例对象,保证此对象为类加载过程中创建的实例对象,即可防止 反序列化破坏。

    import java.io.FileInputStream;
    import java.io.ObjectInputStream;
    import java.io.Serializable;
    
    class HungrySingleton implements Serializable {
        private static final long serialVersionUID = 42L;
    
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton;
    
        static {
            System.out.println("start...");  // start...
            singleton = new HungrySingleton();
        }
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
            if (singleton != null) {
                throw new RuntimeException("实例已存在,不允许重复创建");
            }
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    
        // 定义 readResolve() 方法,返回类加载过程中创建的实例对象(反序列化时返回此对象)
        private Object readResolve() {
            return singleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
    //        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
    //        oos.writeObject(HungrySingleton.getInstance());
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
            HungrySingleton singleton = (HungrySingleton) ois.readObject();
    
            HungrySingleton singleton2 = HungrySingleton.getInstance();
            System.out.println(singleton == singleton2); // true,为同一对象
        }
    }

    4、克隆破坏

    (1)克隆破坏
      如下代码所示,通过克隆创建了个对象。
      调用了 Object 的 clone 方法(native 方法),与反序列化类似,也没有触发 类的构造方法(应该是直接从内存中 copy 了一份)。创建了一个新的对象。
      即 克隆对 单例模式造成了破坏。

    class HungrySingleton implements Cloneable {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton;
    
        static {
            System.out.println("start...");  // start...
            singleton = new HungrySingleton();
        }
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
            if (singleton != null) {
                throw new RuntimeException("实例已存在,不允许重复创建");
            }
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    
        // 重写 clone() 方法,返回 clone 对象
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            HungrySingleton singleton = HungrySingleton.getInstance();
            HungrySingleton singleton2 = (HungrySingleton) singleton.clone();
            System.out.println(singleton == singleton2); // false,不是同一对象
        }
    }

    (2)防止克隆破坏
      保证 clone() 方法返回的对象为类加载过程中创建的实例对象,即可防止 克隆破坏。

    class HungrySingleton implements Cloneable {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static HungrySingleton singleton;
    
        static {
            System.out.println("start...");  // start...
            singleton = new HungrySingleton();
        }
    
        // 构造器私有化(防止通过new创建实例对象)
        private HungrySingleton() {
            if (singleton != null) {
                throw new RuntimeException("实例已存在,不允许重复创建");
            }
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static HungrySingleton getInstance() {
            return singleton;
        }
    
        // 重写 clone() 方法,返回 clone 对象
        @Override
        public Object clone() throws CloneNotSupportedException {
            return singleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            HungrySingleton singleton = HungrySingleton.getInstance();
            HungrySingleton singleton2 = (HungrySingleton) singleton.clone();
            System.out.println(singleton == singleton2); // true,是同一对象
        }
    }

    5、枚举

    (1)基本说明

    【基本说明:】
        写个简单的 enum 类,然后反编译一下 javap -c xx.class。
        可以看到底层就类似于 饿汉式 静态代码块 的写法。在类加载的初始化阶段完成实例化操作。
    
    【优点:】
        在类加载的初始化阶段完成了实例化,仅加载一次。保证对象的唯一性 以及 线程安全。
        可以防止 克隆、反序列化、反射 破坏单例模式。
        写法简单。

    (2)反编译一下 enum 类

    【EnumSingleton】
    enum EnumSingleton {
        INSTANCE;
    }
    
    【javap -c EnumSingleton.classfinal class pattern.sington.EnumSingleton extends java.lang.Enum<pattern.sington.EnumSingleton> {
      public static final pattern.sington.EnumSingleton INSTANCE;
    
      public static pattern.sington.EnumSingleton[] values();
        Code:
           0: getstatic     #1                  // Field $VALUES:[Lpattern/sington/EnumSingleton;
           3: invokevirtual #2                  // Method "[Lpattern/sington/EnumSingleton;".clone:()Ljava/lang/Object;
           6: checkcast     #3                  // class "[Lpattern/sington/EnumSingleton;"
           9: areturn
    
      public static pattern.sington.EnumSingleton valueOf(java.lang.String);
        Code:
           0: ldc           #4                  // class pattern/sington/EnumSingleton
           2: aload_0
           3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
           6: checkcast     #4                  // class pattern/sington/EnumSingleton
           9: areturn
    
      static {};
        Code:
           0: new           #4                  // class pattern/sington/EnumSingleton
           3: dup
           4: ldc           #7                  // String INSTANCE
           6: iconst_0
           7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
          10: putstatic     #9                  // Field INSTANCE:Lpattern/sington/EnumSingleton;
          13: iconst_1
          14: anewarray     #4                  // class pattern/sington/EnumSingleton
          17: dup
          18: iconst_0
          19: getstatic     #9                  // Field INSTANCE:Lpattern/sington/EnumSingleton;
          22: aastore
          23: putstatic     #1                  // Field $VALUES:[Lpattern/sington/EnumSingleton;
          26: return
    }
    
    【等价于:】
    public final class EnumSingleton extends Enum< EnumSingleton> {
        public static final EnumSingleton INSTANCE;
        public static EnumSingleton[] values();
        public static EnumSingleton valueOf(String s);
        static {
            INSTANCE = new EnumSingleton(name, ordinal);
        };
    }

    (3)防止反射破坏
      枚举类型的类,没有无参构造。默认继承 Enum 的有参构造。

    【代码实现:】
    import java.lang.reflect.Constructor;
    
    enum EnumSingleton {
        INSTANCE;
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Class<EnumSingleton> enumSingletonClass = EnumSingleton.class;
            Constructor<EnumSingleton> enumSingletonConstructor = enumSingletonClass.getDeclaredConstructor(String.class, int.class);
            enumSingletonConstructor.setAccessible(true);
            // newInstance 会出现异常,java.lang.IllegalArgumentException: Cannot reflectively create enum objects
            EnumSingleton enumSingleton = enumSingletonConstructor.newInstance();  
    
            EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE;
    
            System.out.println(enumSingleton == enumSingleton2);
        }
    }
    
    【原因分析:】
    newInstance() 方法中进行判断,若为枚举类型,则抛异常。
    
    @CallerSensitive
    public T newInstance(Object ... initargs) throws IllegalArgumentException
    {
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
    }

    (4)防止克隆破坏
      枚举类型的类,无法重写 clone() 方法。其父类 Enum 中定义 clone() 方法为 final 类型,不能被子类重写。

    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    (5)防止序列化破坏
      序列化返回的是同一个对象,无需定义 readResolve() 方法。其执行的是另一个逻辑。

    【代码实现:】
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    enum EnumSingleton {
        INSTANCE;
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
    
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
            oos.writeObject(enumSingleton);
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
            EnumSingleton enumSingleton2 = (EnumSingleton) ois.readObject();
    
            System.out.println(enumSingleton == enumSingleton2); // true,是同一个对象
        }
    }
    
    【反序列化核心代码:】
    ObjectInputStream 中的 readEnum() 方法。
    读入并返回枚举常量,如果枚举类型不可解析,则返回 nullprivate Enum<?> readEnum(boolean unshared) throws IOException {
    
        ObjectStreamClass desc = readClassDesc(false);
    
        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        
        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }
    
        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

    三、懒汉式

    1、实现

    (1)基本说明

    【核心思路:】
        使用 private 修饰 构造方法,保证构造方法私有化。
        提供一个静态的公共方法,在调用该方法时,才去创建实例对象。(全局访问点,通过 "类名.方法名" 获取对象)。    
     
    【可用方式:】
        静态方法
        synchronized 同步方法
        synchronized 同步代码块
        双重检查
        静态内部类
    
    【优点:】
        懒加载,需要使用对象时才会去实例化操作,提高内存利用率。
    
    【缺点:】
        多线程环境下,多个线程可能同时使用对象,需要考虑线程安全问题,防止并发访问生成多个实例。

    (2)代码实现(静态方法)
      如下代码所示,只允许通过 "类名.方法名" 的方式获取对象。

    class FullSingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,通过 "类名.变量名" 访问
        public static FullSingleton getInstance() {
            if (fullSingleton == null) {
                fullSingleton = new FullSingleton();
            }
            return fullSingleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            FullSingleton fullSingleton = FullSingleton.getInstance();
            FullSingleton fullSingleton2 = FullSingleton.getInstance();
            System.out.println(fullSingleton == fullSingleton2); // true,是同一个对象
        }
    }

    (3)这就完了吗?
      当然不是了,这样写只是在单线程环境下正常执行。多线程操作下,会出现多个实例。
    比如:
      线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,执行 new 实例化操作,此时便会产生多个实例对象。
      如下代码所示,代码执行多次可以发现,两个线程输出的对象并不一致。
      此时单例模式被破坏,线程不安全。

    class FullSingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
        public static FullSingleton getInstance() {
            if (fullSingleton == null) {
                fullSingleton = new FullSingleton();
            }
            return fullSingleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@f3f9f4b
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@26af6bb1
                }
            });
    
            thread.start();
            thread2.start();
        }
    }

    (4)代码实现(synchronized 同步方法)
      为了保证线程安全,可以使用 synchronized 关键字实现同步。
    注:
      synchronized 保证同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
      如下代码所示,在方法上添加一个 synchronized,代码执行多次可以发现,两个线程输出的对象始终一致。

    class FullSingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
        public static synchronized FullSingleton getInstance() {
            if (fullSingleton == null) {
                fullSingleton = new FullSingleton();
            }
            return fullSingleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@40788638
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@40788638
                }
            });
    
            thread.start();
            thread2.start();
        }
    }

    (5)这就完了吗?
      当然不是了,虽然使用 synchronized 保证线程安全,但是这种方式锁粒度太大,可能会导致执行效率低。

    (6)代码实现(synchronized 同步代码块)
      如下代码所示,为了缩小 synchronized 影响范围,可以在方法内部使用同步代码块的方式实现。

    class FullSingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
        public static FullSingleton getInstance() {
            if (fullSingleton == null) {
                synchronized(FullSingleton.class) {
                    fullSingleton = new FullSingleton();
                }
            }
            return fullSingleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@5eb39c2b
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@401a5cff
                }
            });
    
            thread.start();
            thread2.start();
        }
    }

    (7)这就完了吗?
      当然不是了,这样写又回到了 静态方法中 提到的 线程不安全的问题上了。
    比如:
      线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,遇到 synchronized,同步执行后,仍会执行 new 操作,产生多个实例对象。
      此时单例模式被破坏,线程不安全。双重检查可以解决这个问题。

    2、双重检查

    (1)代码实现
      如下代码所示,双重检查,在 synchronized 同步代码块 的基础上,再添加一个判断。

    class FullSingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
        public static FullSingleton getInstance() {
            if (fullSingleton == null) {
                synchronized(FullSingleton.class) {
                    if (fullSingleton == null) {
                        fullSingleton = new FullSingleton();
                    }
                }
            }
            return fullSingleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance());
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance());
                }
            });
    
            thread.start();
            thread2.start();
        }
    }

    (2)这就完了吗?
      当然不是了。这样写看上去是保证了线程安全,但是有个细节需要思考一下(指令重排)。
      如下所示,反编译一下代码,可以看到实例化操作的相关指令。

    【Test.java】
    public class Test {
        public static void main(String[] args) {
            Test test = new Test();
        }
    }
    
    【javap -c Test.classpublic class pattern.sington.Test {
      public pattern.sington.Test();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #2                  // class pattern/sington/Test
           3: dup
           4: invokespecial #3                  // Method "<init>":()V
           7: astore_1
           8: return
    }
    
    【关注 main 函数:】
        new 指令在 堆内存中为 Test 对象分配内存空间。
        invokespecial 指令,执行实例初始化操作。
        astore_1 指令,将栈顶引用类型值存入变量(即 使对象指向 堆内存空间)。
    即分为三步:
        1、分配内存空间。
        2、实例初始化
        3、实例指向内存空间
    注:
        按照常理说,1、2、3 是按照顺序执行的。
        但是 JVM 会根据处理器特性,对指令进行优化(指令重排序),从而提高性能。
        指令重排,意味着指令可能不会按照指定顺序执行。
        
    【回到上例的 双重检查的代码:】
        发生指令重排,new 实例化操作按照 1、3、2 的顺序执行。
    假设线程 A 执行完 1、3,但 2 还未执行完,即对象已指向内存空间,但是还没有初始化。
    此时线程 B 执行 getInstance() 代码,由于对象已指向内存空间,判断对象是否为 null 时返回 false, 跳过 synchronized 代码块。
    此时线程 B 拿到的实例对象,由于初始化并未完成,使用对象将可能出现错误(引用逃逸)。
    注: synchronized 并非原子性操作,可能发生指令重排。 使用 voliate 可以通过 内存屏障 禁止指令重排序。

    (3)代码实现(voliate )
      使用 voliate 修饰 变量,禁止指令重排序。

    class FullSingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        // volatile 防止指令重排
        private static volatile FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
        public static FullSingleton getInstance() {
            if (fullSingleton == null) {
                synchronized(FullSingleton.class) {
                    if (fullSingleton == null) {
                        fullSingleton = new FullSingleton();
                    }
                }
            }
            return fullSingleton;
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@79af4c1c
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@79af4c1c
                }
            });
    
            thread.start();
            thread2.start();
        }
    }

    3、静态内部类

    (1)基本说明
      静态内部类是一种结合了 饿汉模式、懒汉模式 优点的实现方式。

    【核心思路:】
        使用 private 修饰 构造方法,保证构造方法私有化。
        在类的内部定义一个静态内部类(只有被调用时,才会被加载),并在内部类中实例化对象。
        提供一个静态的公共方法,在调用该方法时,调用静态内部类。(全局访问点,通过 "类名.方法名" 获取对象)。    
     
    【优点:】
        定义内部类,只有在用到的时候才回去加载,实现懒加载。
        使用 static 定义内部类,利用 JVM 类加载机制保证 线程安全。

    (2)代码实现
      如下代码所示,定义一个静态内部类。

    class FullSingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
        public static FullSingleton getInstance() {
            return InnerInstance.INSTANCE;
        }
    
        // 定义静态内部类
        private static class InnerInstance {
            public static final FullSingleton INSTANCE = new FullSingleton();
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@2a75ae17
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@2a75ae17
                }
            });
    
            thread.start();
            thread2.start();
        }
    }

    (3)这就完了吗?
      当然不是了。反序列化破坏、反射破坏、克隆破坏 的问题同样存在。
      解决方式与 饿汉模式的解决方式类似。

    (4)防止序列化破坏
      重写 readResolve() 方法,返回实例对象。

    import java.io.*;
    
    class FullSingleton implements Serializable {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
        public static FullSingleton getInstance() {
            return InnerInstance.INSTANCE;
        }
    
        // 定义静态内部类
        private static class InnerInstance {
            public static final FullSingleton INSTANCE = new FullSingleton();
        }
    
        private Object readResolve() {
            return getInstance();
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            FullSingleton fullSingleton = FullSingleton.getInstance();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
            oos.writeObject(fullSingleton);
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
            FullSingleton fullSingleton2 = (FullSingleton) ois.readObject();
            System.out.println(fullSingleton == fullSingleton2);
        }
    }

    (5)防止克隆破坏
      重写 clone() 方法,返回实例对象。

    class FullSingleton implements Cloneable {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
        }
    
        // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
        public static FullSingleton getInstance() {
            return InnerInstance.INSTANCE;
        }
    
        // 定义静态内部类
        private static class InnerInstance {
            public static final FullSingleton INSTANCE = new FullSingleton();
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return getInstance();
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            FullSingleton fullSingleton = FullSingleton.getInstance();
            FullSingleton fullSingleton2 = (FullSingleton) fullSingleton.clone();
            System.out.println(fullSingleton == fullSingleton2);
        }
    }

    (6)防止反射破坏
      在构造方法中,新增一个判断。

    import java.lang.reflect.Constructor;
    
    class FullSingleton {
        // 私有化变量,不可以通过 "类名.变量名" 的形式访问
        private static FullSingleton fullSingleton;
    
        // 构造器私有化(防止通过new创建实例对象)
        private FullSingleton () {
            if (getInstance() != null) {
                throw new RuntimeException("实例已存在,创建失败");
            }
        }
    
        // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
        public static FullSingleton getInstance() {
            return InnerInstance.INSTANCE;
        }
    
        // 定义静态内部类
        private static class InnerInstance {
            public static final FullSingleton INSTANCE = new FullSingleton();
        }
    }
    
    public class Test {
        public static void main(String[] args) throws Exception {
            Class<FullSingleton> fullSingletonClass = FullSingleton.class;
            Constructor<FullSingleton> fullSingletonConstructor = fullSingletonClass.getDeclaredConstructor();
            fullSingletonConstructor.setAccessible(true);
            FullSingleton fullSingleton = fullSingletonConstructor.newInstance();
    
            FullSingleton fullSingleton2 = FullSingleton.getInstance();
            System.out.println(fullSingleton == fullSingleton2);
        }
    }

    四、举例

    1、JDK中的单例模式举例(Runtime)

    (1)部分源码
      如下代码所示,就是 饿汉模式的 实现。

    public class Runtime {
        private static Runtime currentRuntime = new Runtime();
        public static Runtime getRuntime() {
            return currentRuntime;
        }
        private Runtime() {} 
    }
    别把自己太当回事,也别太把自己不当回事!Life is Fantastic!!!
  • 相关阅读:
    TCP流量控制,拥塞控制原理
    Java数组--求一个数组中连续m个数的和最大的数组组合
    一次使用IDEA编写JDK动态代理Class数组中有关泛型的问题
    Java数组--一个整型数组,给定一个定数,求数组中两个数的和与定数相等
    Java基础知识--Stream接口的理解与应用
    JSAP107
    JSAP106
    JSAP105
    JSAP104
    JSAP103
  • 原文地址:https://www.cnblogs.com/l-y-h/p/15578922.html
Copyright © 2011-2022 走看看