众所周知(也许有些人不知道),编译器以及CPU会在不影响串行结果的情况下对代码进行重排序,以便加快执行速度。(比如超标量流水线技术)
这在并发执行中会造成一些问题。以单例模式的DCL写法举例:
public class Singleton{
private Singleton(){};
public static volatile Singleton singleton;
public static Singleton getSingleton(){
if(singleton == null){
synchronized(singleton){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检查加锁好理解,一是为了保证单一实例,而是为了减少并发开销。为什么又用了volatile了呢?
因为橙色标注的部分实际上有三步:
分配内存,初始化对象, 引用赋值。
但由于重排序的存在,有可能会先分配内存,然后赋值,最后才初始化。并发下就有可能将未初始化的对象暴露出来。这也是为什么要用volatile的原因。
jdk1.5后, volatile语义中有了happen-before关系约束。保证了对volatile对象的写操作,一定在对其后续的读操作前完成。
也就杜绝了这种重排序造成错误的情况。