前段时间的面试里面,被问到volatile为什么要用到双检锁(Double Check Lock)单例中?没答上-.-||后来google了下,这个问题的回答还是很多的。我们都知道,volatile的作用:
用在多线程,同步变量。 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致 的情况。volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A)
但是,DCL单例模式中为什么要用volatile修饰单例变量呢?下面是一段DCL单例的代码:
public class Singleton{
private int data = 1;//初始化变量
private static volatile singleton = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
if(singleton == null){
synchronized(this){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
这里的问题是问volatile的作用是怎么在singleton = new Singleton();中体现的。这里涉及到“重排序”。
什么是重排序?
比如,两个指令,(1)int a = 1;(2)int b = 2;如果(1)和(2)之间没有数据依赖,那么编译器和处理器就会对这两个指令进行重排序,也就是可能(2)会比(1)先执行。
回到DCL单例模式,在语句singleton = new Singleton() 中,实际上是分为三个操作:(1)分配singleton对象的内存(2)初始化singleton对象(3)把singleton引用指向singleton对象
现在(2)(3)是没有数据依赖的,那处理器可能会先把singleton引用指向singleton对象,然后在初始化singleton对象。如果这样,在线程1执行完(3)后,线程2判断if(singleton == null)时就会是false,然后返回singleton实例,此时这个实例没有初始化,线程2得不到data的值是1。所以为了避免这个问题,volatile在三步操作中都加了内存屏障这种东西,屏蔽掉处理器的重排序。