我们先来回答这个问题 DCL单例一定需要volatile修饰。
volatile有两个功能
内存可见
防止指令冲排序
这里主要考察volatile第二个功能。在介绍为什么DCL单例一定需要volatile修饰之前,我们先来看一下DCL单例和类实例化过程。
DCL单例
public class SingleExample {
private static volatile SingleExample singleExample = null;
private Long count = 99;
private SingleExample(){}
public static SingleExample getInstance(){
if(singleExample == null){
synchronized (SingleExample.class){
if(singleExample == null){
singleExample = new SingleExample();
}
}
}
return singleExample;
}
}
类实例化过程
这里以TestProcess为例来看一下Java对象实例化需要经过哪些步骤。
代码如下:
public class TestProcess {
public Integer num = 9;
public static void main(String[] args){
TestProcess testProcess = new TestProcess();
}
}
main方法执行完后我们来看一下生成的字节码
上面的字节码命令,我们只需要关注如下几行:
0 new #2
4 invokespecial #3 >
7 astore_1
下面我们来解释这三行的意思
//内存中分配空间,,同时为成员变量赋 '零值',即 num = null
命令1: 0 new #2
// 执行TestProcess类的构造方法
命令2: 4 invokespecial #3 >
// 将testProcess变量与内存生成的对象建立链接
命令3: 7 astore_1
场景解释
好了,解释完对象生成的关键这三步,我们回到主题上,我们来举例个场景来说明为什么DCL单例需要volatile修饰。
现在有两个线程
thread1 拿到了SingleExample类锁,正在实例化对象,并且刚执行完命令1 (此时count = null),将要执行命令2和命令3的时候,cpu发生了指令重排序,此时先执行了命令3。
此时 thread2,执行 if(singleExample == null),发现singleExample 不为null,,返回了singleExample对象。
对于thread2来说singleExample对象是没有意义的,因为该对象并未完成真正的初始化,成员变量count的值是null而不是99。如图所示:
至此DCL单例到底需不需要volatile修饰问题讲解结束,欢迎大家轻踩。