单例模式的几种实现

62 阅读2分钟

单例模式

  • 是否线程安全
  • 是否延迟加载

懒汉式 ,线程不安全

  • 是否延迟加载: 是
  • 是否线程安全: 否
class SingleTonOne{

    private static SingleTonOne instance ;

    public SingleTonOne() {
    }

    public static SingleTonOne getInstance() {
        if (instance == null) {
            instance = new SingleTonOne();
        }
        return instance;
    }
}

懒汉式,线程安全

  • 是否延迟加载: 是
  • 是否线程安全: 是
class SingleTonTwo {
    private static SingleTonTwo instance ;

    public SingleTonTwo() {
    }

    public static synchronized SingleTonTwo getInstance() {
        if (instance == null) {
            instance = new SingleTonTwo();
        }
        return instance;
    }
}

饿汉式,线程安全

  • 是否延迟加载: 否
  • 是否线程安全: 是
class HungrySingleTon{
    //只要类被加载 instance就会被实例化,因此没有延迟加载的特性
    private static HungrySingleTon instance = new HungrySingleTon();

    public HungrySingleTon() {
    }
    public static HungrySingleTon getInstance() {
        return instance;
    }
}

双重锁/双重校验锁

  • 是否延迟加载: 是
  • 是否线程安全: 是
class DoubleCheckSingleTon{

    //volatile 保证变量的可见性,指示JVM,这个变量是共享且不稳定的,每次使用它时都需要到主存中进行读取
    //volatile有两个作用:1.保证变量的可见性 2.禁止jvm的指令重排序
    private volatile static DoubleCheckSingleTon instance = null;

    public DoubleCheckSingleTon() {
    }

    public static DoubleCheckSingleTon getInstance() {
        if (instance != null) {
            return instance;
        }
        synchronized(DoubleCheckSingleTon.class) {
            if (instance == null) {
                instance = new DoubleCheckSingleTon();
                /** instance = new DoubleCheckSingleTon();
                 * 这块代码实际是分为三步:
                 * 1. 为 instance 对象分配内存空间
                 * 2. 初始化 instance
                 * 3.将 instance指向分配的内存地址
                 *
                 * 由于JVM具有指令重排的特性,执行顺序可能是 1-> 3 -> 2 。
                 * 指令重排在单线程下不会出现问题,
                 * 但是在多线程环境下会导致一个线程获得还没有初始化的实例
                 */
            }
        }
        return instance;
    }
}

登记式/静态内部类

  • 是否延迟加载: 是
  • 是否线程安全: 是
class StaticInnerClass {

   private static class SingleTonHolder{
       //StaticInnerClass 被加载的时候, instance不会被实例化 ,
       // 只有显示调用 getInstance 时才会显式装载  SingleTonHolder,此时才会实例化,因此也实现了延迟加载
       private static final StaticInnerClass INSTANCE = new StaticInnerClass();
   }

    public StaticInnerClass() {
    }

    public static StaticInnerClass getInstance(){
        return SingleTonHolder.INSTANCE;
    }
}

枚举

enum EnumSingleton{
    INSTANCE;
}

枚举能防止反射,反射的时候会检查修饰符 if ((clazz.getModifiers() & Modifier.ENUM) != 0)

public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

防止反序列化:
在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,
反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。也就是说,序列化的时候只将 INSTANCE 这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型
因此反序列化后的实例也会和之前被序列化的对象实例相同