java 雙重檢查模式 在並發環境下 兼顧安全和效率
成例(Idiom)是一種代碼層次上的模式,是在比設計模式的層次更具體的層次上的代碼技巧。成例往往與編程語言密切相關。
雙重檢查成例(Double Check Idiom)是從C語言移植過來 的一種代碼模式。
先看一個例子:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
}
寫出這樣的代碼,本意顯然是要保持在整個JVM中只有一個Helper的實例。但非常明顯的是,如果在多線程的環境中運行,上面的代碼可能會有兩個甚至兩個以上的Helper對象被創建出來,從而造成錯誤。為了克服沒有線程安全的缺點,下面給出一個線程安全的例子:
class Foo {
private Helper helper = null;
public
synchronized Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
}
顯然,由於整個靜態工廠方法都是同步化的,因此,不會有兩個線程同時進入這個方法。但是,仔細審查上面的方法會發現,同步化實際上只在helper變量第一次被賦值之前才有用。在helper變量有了值以后,同步化實際上變成了一個不必要的瓶頸。如果能有一個方法去掉這個小小的額外開銷,不是更加完美了嗎?因此,就有了下面這個設計“巧妙”的
雙重檢查成例。
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (help == null) {
helper = new Helper();
}
}
}
return helper;
}
}
可以看到,在上面的方法中,同步化僅用來避免多個線程同時初始化這個類,而不是同時調用這個靜態工廠方法。如果這是正確的,那么使用這一個成例之后,“懶漢式”單例類就可以擺脫掉同步化瓶頸,達到一個很妙的境界,如下述代碼:
public class LazySingleton {
private static LazySingleton m_instance = null;
private LazySingleton() { }
private static LazySingleton m_instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (m_instance == null) {
synchronized(LazySingleton.class) {
if (m_instance == null) {
m_instance = new LazySingleton();
}
}
}
return m_instance;
}
if (m_instance == null) {
synchronized(LazySingleton.class) {
if (m_instance == null) {
m_instance = new LazySingleton();
}
}
}
return m_instance;
}
}
令人吃驚的是,在C語言里得到普遍應用的
雙重檢查成例在多數的Java語言編譯器里面並不成立。上面使用了
雙重檢查成例的“懶漢式”單例類,不能工作的基本原因在於,在Java編譯器中,
LazySingleton類的初始化與m_instance變量賦值的順序不可預料。如果一個線程在沒有同步化的條件下讀取m_instance引用,並調用這個對象的方法的話,可能會發現對象的初始化過程尚未完成,從而造成崩潰。
一般而言,
雙重檢查成例對Java語言來說是不成立的。在一般情況下,使用餓漢式單例模式或者對整個靜態工廠方法同步化的懶漢式單例模式足以解決在實際設計工作中遇到的問題。