这是一道美团的面试题
DCL单例(Double Check Lock)到底需不需要 volatile
其实就是问:private static volatile Singleton singleton = null; 中的 volatile 有没有必要加?
代码
public class Singleton {
// 使用volatile保证了多线程访问时singleton变量的可见性,
// 避免了初始化时其他变量属性还没赋值完时,被另外线程调用
private static volatile Singleton singleton = null;
// 定义一个私有构造,防止通过new去实例化
private Singleton(){}
public static Singleton getSingLeton(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
为了理解这个问题,得深入理解对象创建得过程(new)
比如这个代码
public static void main( String[] args )
{
Object o = new Object();
}
0: new #1; (半初始化)
4: invokespecial #1 <java/lang/Object.<init>> (调用构造方法,实例变量赋值)
7: astore_1 (与对象构建链接,建立联系)
对象 new 的过程可以分为三步
1)申请一块内存, 处于半初始化状态,如果有成员变量则赋上默认值
2)调用对象得构造方法
3)把 o (栈上得引用)与实际对象建立关联
但 CUP 会发生指令重排序得现象。
正常的对象生成 是 1-》2-》-》3
重排序了则 1 -》3-》2
后果是什么呢?
A 线程 初始化把对象初始化一半,且建立关联了。
这时候 B 线程来了,欸,它一看这个对象不为 null ,那么它就拿来用了。
if(o !=null) xxx -> 使用了一个半初始化对象
所以问题就是出在这里!!!