DCL 和 乱序

在很多设计模式的书籍中,我们都可以看到类似下面的单例模式的实现代码,一般称为Double-checked locking(DCL)

01 public class Singleton {
02  
03     private static Singleton instance;
04  
05     private Singleton() {
06         // do something
07     }
08  
09     public static Singleton getInstance() {
10         if (instance == null) {//1
11             synchronized (Singleton.class) {//2
12                 if (instance == null) {//3
13                     instance = new Singleton();//4
14                 }
15             }
16         }
17         return instance;
18     }
19 }

这样子的代码看起来很完美,可以解决instance的延迟初始化。只是,事实往往不是如此。

问题在于instance = new Singleton();这行代码。

在我们看来,这行代码的意义大概是下面这样子的

 

 

	mem = allocate();             //收集内存
ctorSingleton(mem);      //调用构造函数
instance = mem;               //把地址传给instance
	

 

这行代码在Java虚拟机(JVM)看来,却可能是下面的三个步骤(乱序执行的机制):

 

	mem = allocate();             //收集内存
instance = mem;               //把地址传给instance
	ctorSingleton(instance);      //调用构造函数

 

下面我们来假设一个场景。

  1. 线程A调用getInstance函数并且执行到//4。但是线程A只执行到赋值语句,还没有调用构造函数。此时,instance已经不是null了,但是对象还没有初始化。
  2. 很不幸线程A这时正好被挂起。
  3. 线程B获得执行的权力,然后也开始调用getInstance。线程B在//1发现instance已经不是null了,于是就返回对象了,但是这个对象还没有初始化,于是对这个对象进行操作就出错了。

问题就出在instance被提前初始化了。

解决方案一,不使用延迟加载:

01 public class Singleton {
02  
03     private static Singleton instance = new Singleton();
04  
05     private Singleton() {
06         // do something
07     }
08  
09     public static Singleton getInstance() {
10         return instance;
11     }
12 }

JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。

解决方案二,利用一个内部类来实现延迟加载:

01 public class Singleton {
02  
03     private Singleton() {
04         // do something
05     }
06  
07     private static class SingletonContainer {
08         private static Singleton instance = new Singleton();
09     }
10  
11     public static Singleton getInstance() {
12         return SingletonContainer.instance;
13     }
14 }

这两种方案都是利用了JVM的类加载机制的互斥。

方案二的延迟加载实现是因为,只有在第一次调用Singleton.getInstance()函数时,JVM才会去加载SingletonContainer,并且初始化instance。

不只Java存在这个问题,C/C++由于CPU的乱序执行机制,也同样存在这样的问题。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章