【轉】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

相關文章
相關標籤/搜索