首先解释一下什么是DCL,DCL是Double Check Lock的简称,是单例模式的一种加锁双重检查写法,代码如下:
package com.test.util;
public class TestDCL {
private volatile static TestDCL instance;
private TestDCL() {
}
public static TestDCL getInstance() {
if (instance == null) {
synchronized (TestDCL.class) {
if (instance == null) {
instance = new TestDCL();
}
}
}
return instance;
}
public static void main(String[] args) {
TestDCL o = TestDCL.getInstance();
}
}
下面进入正题:DCL下,为什么需要给instance加上volatile关键字? 答:因为Java里创建对象,JVM指令是多条,例如:
Object o = new Object();
这个创建对象代码的JVM的指令是这样的

new #2 : 这行指令是说在堆上的某个地址处开辟了一块空间作为Object对象
invokespecial #1 :这行指令是说将对象里的成员变量进行赋值操作
astore_1 :这行指令是说将栈里的Objec o 与 堆上的对象建立起引用关联
而 invokespecial #1 指令, 和 astore_1 指令, 是可以乱序的, 就是说可以颠倒顺序,有可能 astore_1 先执行, invokespecial #1 后执行。 因为在堆上建立对象开辟地址以后,地址就已经定了,而 “将栈里的Objec o 与 堆上的对象建立起引用关联” 和 “将对象里的成员变量进行赋值操作” 是没什么逻辑关系的。所以cpu可以进行乱序执行,只要程序最终的结果是一致的就可以。 这种情况,在单线程下没有问题,但是多线程下,就会出现错误。
试想一下,DCL下,线程A在将对象new出来的时,刚执行完 new #2 指令,紧接着没有执行 invokespecial #1 指令,而是执行了 astore_1 ,也就是说发生了乱序执行。 此时线程B进入getInstance() ,发现instance并不为空(因为已经有了引用指向了对象,只不过还没来得及给对象里的成员变量赋值),然后线程B便直接return了一个“半初始化”对象(对象还没彻底创建完)。
所以DCL里,需要给instance加上volatile关键字,因为volatile在JVM层有一个特性叫内存屏障,可以防止指令重排序,从而保证了程序的正确性。