首先什么是DCL,就是单例的一种模式,double check lock缩写
private static volatile Mrg INSTANCE;
private int m = 8;
private Mrg(){}
public static Mrg getInstance(){
if (INSTANCE == null){
synchronized (Mrg.class) {
if (INSTANCE == null) {
INSTANCE = new Mrg();
}
}
}
return INSTANCE;
}
其次需要了解一段简单代码的汇编
public class Test {
public static void main(String[] args) {
T t = new T();
}
}
class T{
int m = 8;
}
/**
对应的汇编
NEW cn/itcast/hotel/T 分配内存空间,给m设置默认值0
DUP 这里不涉及
INVOKESPECIAL cn/itcast/hotel/T.<init> ()V 调用无参构造,给m初始化值8
ASTORE 1 将对象与t挂钩
RETURN 这里不涉及
**/
最后我们需要知道什么是指令重排
指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序(汇编级别).
规则简化一下就是这段代码某几行换顺序后最后结果不影响,就有可能那样换。
总结
根据上面两个代码片段,我们简单理解了DCL,就是一种单例模式;
然后new 一个对象汇编会有三个关键步骤:
- 分配内存空间,给m设置默认值0
- 调用无参构造,给m初始化值8
- 将新对象与变量挂钩
根据指令重排规则,我们就会发现上面三个步骤中,2,3换顺序不影响最后结果,这在DCL中就会存在问题
public static Mrg getInstance(){
if (INSTANCE == null){
synchronized (Mrg.class) {
if (INSTANCE == null) {
//1.第一个线程在指令重排后执行了1、3两步然后停止,这个时候m=0;
//2.另一个线程在第一次判断INSTANCE == null时,发现不满足,于是直接返回
// 还没有初始化但给了默认值的对象。
INSTANCE = new Mrg();
}
}
}
return INSTANCE;
}
- 第一个线程在指令重排后执行了1、3两步然后停止,这个时候m=0;
- 另一个线程在第一次判断INSTANCE == null时,发现不满足,于是直接返回 还没有初始化但给了默认值的对象。
- 所以需要通过添加volatile关键字来防止指令重排。