雙重檢查鎖定模式

雙重檢查鎖定模式（也被稱為"雙重檢查加鎖優化"，"鎖暗示"（Lock hint）) 是一種軟件設計模式用來減少併發系統中競爭和同步的開銷。雙重檢查鎖定模式首先驗證鎖定條件(第一次檢查)，只有通過鎖定條件驗證才真正的進行加鎖邏輯並再次驗證條件(第二次檢查)。
該模式在某些語言在某些硬件平臺的實現可能是不安全的。有的時候，這一模式被看做是反模式。
它通常用於減少加鎖開銷，尤其是為多線程環境中的單例模式實現“惰性初始化”。惰性初始化的意思是直到第一次訪問時才初始化它的值。

**Java中的使用**
考慮下面的Java代碼date=20120306104259 
// Single threaded version
class 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 version
class Foo {
private Helper helper = null;
public synchronized Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// other functions and members...
}
只有getHelper()的第一次調用需要同步創建對象，創建之後getHelper()只是簡單的返回成員變量，而這裡是無需同步的。
由於同步一個方法會降低100倍或更高的性能， 每次調用獲取和釋放鎖的開銷似乎是可以避免的：一旦初始化完成，獲取和釋放鎖就顯得很不必要。許多程序員以下面這種方式進行優化：
# 檢查變量是否被初始化(不去獲得鎖)，如果已被初始化立即返回這個變量。
# 獲取鎖
# 第二次檢查變量是否已經被初始化：如果其他線程曾獲取過鎖，那麼變量已被初始化，返回初始化的變量。
# 否則，初始化並返回變量。
// Broken multithreaded version
// "Double-Checked Locking" idiom
class 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...
}
直覺上，這個算法看起來像是該問題的有效解決方案。然而，這一技術還有許多需要避免的細微問題。例如，考慮下面的事件序列：
# 線程A發現變量沒有被初始化, 然後它獲取鎖並開始變量的初始化。
# 由於某些編程語言的語義，編譯器生成的代碼允許在線程A執行完變量的初始化之前，更新變量並將其指向部分初始化的對象。
# 線程B發現共享變量已經被初始化，並返回變量。由於線程B確信變量已被初始化，它沒有獲取鎖。如果在A完成初始化之前共享變量對B可見（這是由於A沒有完成初始化或者因為一些初始化的值還沒有覆蓋B使用的內存(緩存一致性)），程序很可能會崩潰。
在J2SE 1.4或更早的版本中使用雙重檢查鎖有潛在的危險，有時會正常工作：區分正確實現和有小問題的實現是很困難的。取決於編譯器，線程的調度和其他併發系統活動，不正確的實現雙重檢查鎖導致的異常結果可能會間歇性出現。重現異常是十分困難的。
在J2SE 5.0中，這一問題被修正了。volatile關鍵字保證多個線程可以正確處理單件實例。date=20120306104259 描述了這一新的語言特性:
// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
Helper result = helper;
if (result == null) {
synchronized(this) {
result = helper;
if (result == null) {
helper = result = new Helper();
}
}
}
return result;
}
// other functions and members...
}
注意局部變量result的使用看起來是不必要的。對於某些版本的Java虛擬機，這會使代碼提速25%，而對其他的版本則無關痛癢。
如果helper對象是靜態的(每個類只有一個), 可以使用雙重檢查鎖的替代模式惰性初始化模式。查看 上的列表16.6。
// Correct lazy initialization in Java
@ThreadSafe
class Foo {
private static class HelperHolder {
public static Helper helper = new Helper();
}
public static Helper getHelper() {
return HelperHolder.helper;
}
}
這是因為內部類直到他們被引用時才會加載。
Java 5中的final語義可以不使用volatile關鍵字實現安全的創建對象：
public class FinalWrapper {
public final T value;
public FinalWrapper(T value) {
this.value = value;
}
}
public class Foo {
private FinalWrapper helperWrapper = null;
public Helper getHelper() {
FinalWrapper wrapper = helperWrapper;
if (wrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper(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可能會影響程序的性能。例如，如果指針定義對代碼的其他地方可見，強制編譯器將指針視為屏障，就會降低程序的性能，這是完全不必要的。

**參見**
測試和測試並設置idiom作為底層加鎖機制。
*按需初始化持有者作為Java中線程安全的替代者。

**參考資料**

**外部鏈接**
 Issues with the double checked locking mechanism captured in  Jeu George's Blogs  Pure Virtuals- Implementation of Various Singleton Patterns including the  Double Checked Locking Mechanismdate=20120204211315  at  TEKPOOLdate=20120204210751 - "Double Checked Locking" Description from the Portland Pattern Repository- "Double Checked Locking is Broken" Description from the Portland Pattern Repository- Paper " C++ and the Perils of Double-Checked Lockingdate=20120207111747 " (475 KB) by Scott Meyers and Andrei Alexandrescu- Article " Double-checked locking: Clever, but broken" by Brian Goetz-  The "Double-Checked Locking is Broken" Declarationdate=20120306104259 ; David Bacon et al.-  Double-checked locking and the Singleton patterndate=20050626074921 -  Singleton Pattern and Thread Safety-  volatile keyword in VC++ 2005date=20080417193529 -  Java Examples and timing of double check locking solutions