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的亂序執行機制,也同樣存在這樣的問題。


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