单例模式
一、饿汉式简单
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;
}
}

浙公网安备 33010602011771号