很多懒汉模式写成如下模式:
public class LazySingle {
private static LazySingle single;
private LazySingle(){}
public static LazySingle getInstance(){
if(single == null){
synchronized (LazySingle.class) {
//对类对象加锁,所有对象均不能同时运行。
if(single == null){
single = new LazySingle();
}
}
}
return single;
}
}
这里分析多线程下的情况。
主要问题在于 single = new LazySingle();
这句,这并非一个原子操作,这段代码其实是分为三步执行:
- 为 LazySingle 分配内存空间,对内存清零
- 执行 LazySingle 的构造函数,初始化对象
- 将 single 指向分配的内存地址(执行完这步 single就为非 null 了)
看起来并没有问题,想想,在多线程下也没有问题,但这里有个坑,那就是指令重排序的问题。
由于CPU运行速度远比内存快,所以为了优化Java运行速度,在没有数据依赖性的前提下,编译器可能对指令重排序,先执行CPU自己就可以执行的指令。这种优化对单线程没有影响,对多线程可能产生影响。
也就是说这三步发生的前后顺序不能保证,不符合先行发生原则。执行顺序有可能变成 1>3>2。
例如,线程 T1 执行了 1 和 3,此时线程 T2 调用 getInstance() 后发现 single 不为空,因此返回 single,但此时 single 还未被初始化。(因为 Lazysingle 的构造函数还未执行)
解决方法就是将 single 变量声明成 volatile ,让其符合JMM中先行发生原则的 volatile 变量规则。
public class LazySingle {
private volatile static LazySingle single;
private LazySingle(){}
public static LazySingle getInstance(){
if(single == null){
synchronized (LazySingle.class) {
//对类对象加锁,所有对象均不能同时运行。
if(single == null){
single = new LazySingle();
}
}
}
return single;
}
}
这里 volatile 可以禁止指令重排序。“也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。”
volatile的更多详细内容 -> 查找本博客中的另一篇文章 Java 中 volatile 关键字解析