雙重檢查鎖定模式

雙重檢查鎖定模式(也被稱為"雙重檢查加鎖最佳化","鎖暗示"(Lock hint)) 是一種軟體設計模式用來減少並發系統中競爭和同步的開銷。雙重檢查鎖定模式首先驗證鎖定條件(第一次檢查),只有通過鎖定條件驗證才真正的進行加鎖邏輯並再次驗證條件(第二次檢查)。

該模式在某些語言在某些硬體平台的實現可能是不安全的。有的時候,這一模式被看做是反模式

它通常用於減少加鎖開銷,尤其是為多執行緒環境中的單例模式實現“惰性初始化”。惰性初始化的意思是直到第一次訪問時才初始化它的值。

基本介紹

Java中的使用,Microsoft Visual C++ 中的使用,參見,

Java中的使用

考慮下面的Java代碼
// Single threaded versionclass Foo {    private Helper helper = null;    public Helper getHelper() {        if (helper == null) {            helper = new Helper();        }        return helper;    }    // other functions and members}
這段在使用多執行緒的情況下無法正常工作。在多個執行緒同時調用getHelper()時,必須要獲取,否則,這些執行緒可能同時去創建對象,或者某個執行緒會得到一個未完全初始化的對象。
鎖可以通過代價很高的同步來獲得,就像下面的例子一樣。
// Correct but possibly expensive multithreaded versionclass Foo {    private Helper helper = null;    public synchronized Helper getHelper() {        if (helper == null) {            helper = new Helper();        }        return helper;    }    // other functions and members}
只有getHelper()的第一次調用需要同步創建對象,創建之後getHelper()只是簡單的返回成員變數,而這裡是無需同步的。 由於同步一個方法會降低100倍或更高的性能, 每次調用獲取和釋放鎖的開銷似乎是可以避免的:一旦初始化完成,獲取和釋放鎖就顯得很不必要。許多程式設計師一下面這種方式進行最佳化:
  1. 檢查變數是否被初始化(不去獲得鎖),如果已被初始化立即返回這個變數。
  2. 獲取鎖
  3. 第二次檢查變數是否已經被初始化:如果其他執行緒曾獲取過鎖,那么變數已被初始化,返回初始化的變數。
  4. 否則,初始化並返回變數。
// Broken multithreaded version// "Double-Checked Locking" idiomclass Foo{    private Helper helper = null;    public Helper getHelper() {        if (helper == null) {            synchronized(this) {                if (helper == null) {                    helper = new Helper();                }            }        }        return helper;    }    // other functions and members}
直覺上,這個算法看起來像是該問題的有效解決方案。然而,這一技術還有許多需要避免的細微問題。例如,考慮下面的事件序列:
  1. 執行緒A發現變數沒有被初始化, 然後它獲取鎖並開始變數的初始化。
  2. 由於某些程式語言的語義,編譯器生成的代碼允許線上程A執行完變數的初始化之前,更新變數並將其指向部分初始化的對象。
  3. 執行緒B發現共享變數已經被初始化,並返回變數。由於執行緒B確信變數已被初始化,它沒有獲取鎖。如果在A完成初始化之前共享變數對B可見(這是由於A沒有完成初始化或者因為一些初始化的值還沒有覆蓋B使用的記憶體(快取一致性)),程式很可能會崩潰。
    在J2SE 1.4或更早的版本中使用雙重檢查鎖有潛在的危險,有時會正常工作:區分正確實現和有小問題的實現是很困難的。取決於編譯器,執行緒的調度和其他並發系統活動,不正確的實現雙重檢查鎖導致的異常結果可能會間歇性出現。重現異常是十分困難的。
在J2SE 5.0中,這一問題被修正了。volatile關鍵字保證多個執行緒可以正確處理單件實例。
public class FinalWrapper<T> {    public final T value;    public FinalWrapper(T value) {        this.value = value;    }}public class Foo {   private FinalWrapper<Helper> helperWrapper = null;   public Helper getHelper() {      FinalWrapper<Helper> wrapper = helperWrapper;      if (wrapper == null) {          synchronized(this) {              if (helperWrapper == null) {                  helperWrapper = new FinalWrapper<Helper>(new Helper());              }              wrapper = helperWrapper;          }      }      return wrapper.value;   }}
為了正確性,局部變數wrapper是必須的。這一實現的性能不一定比使用volatile的性能更高。

Microsoft Visual C++ 中的使用

如果指針是由C++關鍵字volatile定義的,那么雙重檢查鎖可以在Visual C++2005 或更高版本中實現。Visual C++ 2005 保證volatile變數是一種記憶體屏障,阻止編譯器和CPU重新安排讀入和寫出語義。在先前版本的Visual C++則沒有此類保證。在其他方面將指針定義為volatile可能會影響程式的性能。例如,如果指針定義對代碼的其他地方可見,強制編譯器將指針視為屏障,就會降低程式的性能,這是完全不必要的。
雙重檢查鎖定可以在.NET中高效實現。 常見的用法模式是向Singleton實現添加雙重檢查鎖定:
public class MySingleton {        private static object myLock = new object();        private static volatile MySingleton mySingleton = null;     // 'volatile' is unnecessary in .NET 2.0 and later        private MySingleton() { }        public static MySingleton GetInstance() {                if (mySingleton == null) { // 1st check                        lock (myLock) {                                if (mySingleton == null) {                     // 2nd (double) check                                        mySingleton = new MySingleton();         }                       }                 }     return mySingleton;        }}
在此示例中,“lock hint”是mySingleton對象,在完全構造並準備使用時不再為null。
在.NET Framework 4.0中,引入了Lazy <T>類,默認情況下在內部使用雙重檢查鎖定(Execution And Publication模式)來存儲構造期間拋出的異常,或者傳遞給Lazy的函式的結果<T>:
public class MySingleton{        private static readonly Lazy<MySingleton> _mySingleton = new Lazy<MySingleton>(() => new MySingleton());        private MySingleton() { }        public static MySingleton Instance {                get {                            return _mySingleton.Value;                    }        }}

參見

  • 測試和測試並設定idiom作為底層加鎖機制。
  • 按需初始化持有者作為Java中執行緒安全的替代者。

相關詞條

熱門詞條

聯絡我們