关于双重检查锁,看到一篇挺好的文章,转来以备往后复习使用:html
原文:Java中的双重检查锁(double checked locking)多线程
在实现单例模式时,若是未考虑多线程的状况,就容易写出下面的错误代码:性能
public class Singleton { private static Singleton uniqueSingleton; private Singleton() { } public Singleton getInstance() { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } return uniqueSingleton; } }
在多线程的状况下,这样写可能会致使uniqueSingleton
有多个实例。好比下面这种状况,考虑有两个线程同时调用getInstance()
:优化
能够看到,uniqueSingleton
被实例化了两次而且被不一样对象持有。彻底违背了单例的初衷。spa
出现这种状况,第一反应就是加锁,以下:线程
public class Singleton { private static Singleton uniqueSingleton; private Singleton() { } public synchronized Singleton getInstance() { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } return uniqueSingleton; } }
这样虽然解决了问题,可是由于用到了synchronized
,会致使很大的性能开销,而且加锁其实只须要在第一次初始化的时候用到,以后的调用都不必再进行加锁。3d
双重检查锁(double checked locking)是对上述问题的一种优化。先判断对象是否已经被初始化,再决定要不要加锁。code
public class Singleton { private static Singleton uniqueSingleton; private Singleton() { } public Singleton getInstance() { if (null == uniqueSingleton) { synchronized (Singleton.class) { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); // error
} } } return uniqueSingleton; } }
若是这样写,运行顺序就成了:htm
执行双重检查是由于,若是多个线程同时了经过了第一次检查,而且其中一个线程首先经过了第二次检查并实例化了对象,那么剩余经过了第一次检查的线程就不会再去实例化对象。对象
这样,除了初始化的时候会出现加锁的状况,后续的全部调用都会避免加锁而直接返回,解决了性能消耗的问题。
上述写法看似解决了问题,可是有个很大的隐患。实例化对象的那行代码(标记为error的那行),实际上能够分解成如下三个步骤:
可是有些编译器为了性能的缘由,可能会将第二步和第三步进行重排序,顺序就成了:
如今考虑重排序后,两个线程发生了如下调用:
在这种状况下,T7时刻线程B对uniqueSingleton
的访问,访问的是一个初始化未完成的对象。
public class Singleton { private volatile static Singleton uniqueSingleton; private Singleton() { } public Singleton getInstance() { if (null == uniqueSingleton) { synchronized (Singleton.class) { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } } } return uniqueSingleton; } }
为了解决上述问题,须要在uniqueSingleton
前加入关键字volatile
。使用了volatile关键字后,重排序被禁止,全部的写(write)操做都将发生在读(read)操做以前。
还有,在知乎上也看到一个这样的回答:https://www.zhihu.com/question/35268028