单例模式

一、饿汉式简单

public class SingletonV1 {
    private static final SingletonV1 INSTANCE = new SingletonV1();
    private SingletonV1() {
    }
    public static SingletonV1 obtainSingleton(){
        return INSTANCE;
    }
}

二、双重检查

public class SingletonV2 {
  	//使用volatile避免指令重排序问题
    private volatile static SingletonV2 INSTANCE ;
    private SingletonV2() {
    }
    public static synchronized SingletonV2 obtainInstance(){
       if (INSTANCE==null){
            synchronized (SingletonV3.class){
                if (INSTANCE==null){
                    INSTANCE = new SingletonV3();
                }
            }
        }
        return INSTANCE;
    }
}

创建对象的流程可以简化为三步:分配内存空间、初始化对象、设置引用指向分配的内存地址。

memory = allocate();    // 1、分配对象的内存空间
initObject(memory);     // 2、初始化对象
instance = memory;      // 3、设置instance指向分配的内存地址

其中,2 和 3 之间可能被重排序。

重排序之后的流程

memory = allocate();    // 1、分配对象的内存空间
instance = memory;      // 2、设置instance指向分配的内存地址
initObject(memory);     // 3、初始化对象

那么,在发生重排序的情况下,A、B两个线程执行这段双重检查锁定的代码时的执行顺序可能是这样的:

时间 A 线程 B 线程
t1 A1:分配对象的内存空间
t2 A2:设置instance指向内存空间的地址
t3 B1:判断instance是否为null
t4 B2:由于instance不是null,B线程将直接访问instance引用的对象
t5 A3:初始化对象
t6 A4:访问instance引用的对象

按照以上的顺序,B线程将会在instance对象还未初始化完成之前就访问它,导致NPE。

三、静态内部类

public class SingletonV3 {
    private SingletonV3() {
    }
    private static class SingletonHolder{
        private static final SingletonV4 INSTANCE = new SingletonV4();
    }
    public static SingletonV3 obtainInstance(){
        return SingletonHolder.INSTANCE;
    }

}

静态内部类不会随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的。它的初始化过程线程安全问题是由jvm保证的。

四、枚举单例

public enum SingletonV4 {
    INSTANCE;
    public static SingletonV4 obtainInstance(){
        return INSTANCE;
    }
}
posted @ 2021-03-11 11:46  Matteo-Carati  阅读(46)  评论(0)    收藏  举报