【转】Java中的双重检查锁(double checked locking)

 

关于双重检查锁,看到一篇挺好的文章,转来以备往后复习使用: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

  1. 检查变量是否被初始化(不去得到锁),若是已被初始化则当即返回。
  2. 获取锁。
  3. 再次检查变量是否已经被初始化,若是还没被初始化就初始化一个对象。

执行双重检查是由于,若是多个线程同时了经过了第一次检查,而且其中一个线程首先经过了第二次检查并实例化了对象,那么剩余经过了第一次检查的线程就不会再去实例化对象。对象

这样,除了初始化的时候会出现加锁的状况,后续的全部调用都会避免加锁而直接返回,解决了性能消耗的问题。

 

隐患

上述写法看似解决了问题,可是有个很大的隐患。实例化对象的那行代码(标记为error的那行),实际上能够分解成如下三个步骤:

  1. 分配内存空间
  2. 初始化对象
  3. 将对象指向刚分配的内存空间

可是有些编译器为了性能的缘由,可能会将第二步和第三步进行重排序,顺序就成了:

  1. 分配内存空间
  2. 将对象指向刚分配的内存空间
  3. 初始化对象

如今考虑重排序后,两个线程发生了如下调用:

 

 在这种状况下,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

相关文章
相关标签/搜索