確保物件的唯一性——單例模式 (三)
3.4 餓漢式單例與懶漢式單例的討論
Sunny公司開發人員使用單例模式實現了負載均衡器的設計,但是在實際使用中出現了一個非常嚴重的問題,當負載均衡器在啟動過程中使用者再次啟動該負載均衡器時,系統無任何異常,但當客戶端提交請求時出現請求分發失敗,通過仔細分析發現原來系統中還是存在多個負載均衡器物件,導致分發時目標伺服器不一致,從而產生衝突。為什麼會這樣呢?Sunny公司開發人員百思不得其解。
現在我們對負載均衡器的實現程式碼進行再次分析,當第一次呼叫getLoadBalancer()方法建立並啟動負載均衡器時,instance物件為null值,因此係統將執行程式碼instance= new LoadBalancer(),在此過程中,由於要對LoadBalancer進行大量初始化工作,需要一段時間來建立LoadBalancer物件。而在此時,如果再一次呼叫getLoadBalancer()方法(通常發生在多執行緒環境中),由於instance尚未建立成功,仍為null值,判斷條件(instance== null)為真值,因此程式碼instance= new LoadBalancer()將再次執行,導致最終建立了多個instance物件,這違背了單例模式的初衷,也導致系統執行發生錯誤。
如何解決該問題?我們至少有兩種解決方案,在正式介紹這兩種解決方案之前,先介紹一下單例類的兩種不同實現方式,餓漢式單例類和懶漢式單例類。
1.餓漢式單例類
餓漢式單例類是實現起來最簡單的單例類,餓漢式單例類結構圖如圖3-4所示:
class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
2.懶漢式單例類與執行緒鎖定
除了餓漢式單例,還有一種經典的懶漢式單例,也就是前面的負載均衡器LoadBalancer類的實現方式。懶漢式單例類結構圖如圖3-5所示:
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
問題貌似得以解決,事實並非如此。如果使用以上程式碼來實現單例,還是會存在單例物件不唯一。原因如下:假如在某一瞬間執行緒A和執行緒B都在呼叫getInstance()方法,此時instance物件為null值,均能通過instance == null的判斷。由於實現了synchronized加鎖機制,執行緒A進入synchronized鎖定的程式碼中執行例項建立程式碼,執行緒B處於排隊等待狀態,必須等待執行緒A執行完畢後才可以進入synchronized鎖定程式碼。但當A執行完畢時,執行緒B並不知道例項已經建立,將繼續建立新的例項,導致產生多個單例物件,違背單例模式的設計思想,因此需要進行進一步改進,在synchronized中再進行一次(instance == null)判斷,這種方式稱為雙重檢查鎖定(Double-Check Locking)。使用雙重檢查鎖定實現的懶漢式單例類完整程式碼如下所示:
class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判斷
if (instance == null) {
//鎖定程式碼塊
synchronized (LazySingleton.class) {
//第二重判斷
if (instance == null) {
instance = new LazySingleton(); //建立單例例項
}
}
}
return instance;
}
}
需要注意的是,如果使用雙重檢查鎖定來實現懶漢式單例類,需要在靜態成員變數instance之前增加修飾符volatile,被volatile修飾的成員變數可以確保多個執行緒都能夠正確處理,且該程式碼只能在JDK 1.5及以上版本中才能正確執行。由於volatile關鍵字會遮蔽Java虛擬機器所做的一些程式碼優化,可能會導致系統執行效率降低,因此即使使用雙重檢查鎖定來實現單例模式也不是一種完美的實現方式。
|
3.餓漢式單例類與懶漢式單例類比較
餓漢式單例類在類被載入時就將自己例項化,它的優點在於無須考慮多執行緒訪問問題,可以確保例項的唯一性;從呼叫速度和反應時間角度來講,由於單例物件一開始就得以建立,因此要優於懶漢式單例。但是無論系統在執行時是否需要使用該單例物件,由於在類載入時該物件就需要建立,因此從資源利用效率角度來講,餓漢式單例不及懶漢式單例,而且在系統載入時由於需要建立餓漢式單例物件,載入時間可能會比較長。
懶漢式單例類在第一次使用時建立,無須一直佔用系統資源,實現了延遲載入,但是必須處理好多個執行緒同時訪問的問題,特別是當單例類作為資源控制器,在例項化時必然涉及資源初始化,而資源初始化很有可能耗費大量時間,這意味著出現多執行緒同時首次引用此類的機率變得較大,需要通過雙重檢查鎖定等機制進行控制,這將導致系統效能受到一定影響。
【作者:劉偉 http://blog.csdn.net/lovelion】
相關文章
- 確保物件的唯一性——單例模式 (五)物件單例模式
- 確保物件的唯一性——單例模式 (四)物件單例模式
- 確保物件的唯一性——單例模式 (二)物件單例模式
- 設計模式系列之單例模式(Singleton Pattern)——確保物件的唯一性設計模式單例物件
- 單例模式的正確寫法單例模式
- 【指令碼】如何確保應用程式的唯一性指令碼
- 確保您擁有一個獨一無二的例項:單例模式的建立方式單例模式
- 單例模式 - 確定 N 先生的GrilFriend單例模式
- 單例模式三境界單例模式
- Javascript設計模式(三)單例模式JavaScript設計模式單例
- 如何正確地寫出單例模式單例模式
- 第三篇:確保物件在被使用前的初始化物件
- Singleton(單例)——物件建立型模式單例物件模式
- 設計模式快速學習(三)單例模式設計模式單例
- 設計模式(三)——JDK中的那些單例設計模式JDK單例
- Python 建立單例模式的三種方式Python單例模式
- 設計模式(三)----建立型模式之單例模式(一)設計模式單例
- 設計模式之“物件效能模式”: Singleton 單例模式(筆記)設計模式物件單例筆記
- 常見的三種工廠模式區別及單例模式模式單例
- 您的單例模式,真的單例嗎?單例模式
- 【設計模式】三、單例模式(10分鐘深度搞定)設計模式單例
- JAVA物件導向高階:static的應用知識--單例模式 單例設計模式應用場景及好處 單例設計模式的實現方式Java物件單例設計模式
- 單例模式單例模式
- iOS單例物件iOS單例物件
- JS中的單例模式及單例模式原型類的實現JS單例模式原型
- DCL單例模式中的缺陷及單例模式的其他實現單例模式
- 【極客思考】設計模式:你確定你真的理解了單例模式嗎?設計模式單例
- dataguard三種保護模式模式
- Scala單例物件、伴生物件單例物件
- 單例模式(下)---聊一聊單例模式的幾種寫法單例模式
- 單例模式(下) - 聊一聊單例模式的幾種寫法單例模式
- 單例模式(下) – 聊一聊單例模式的幾種寫法單例模式
- Flutter 中的單例模式Flutter單例模式
- Flutter(able) 的單例模式Flutter單例模式
- 單例模式的實現單例模式
- javascript單例模式的理解JavaScript單例模式
- 單例模式static的困惑單例模式
- JavaScript設計模式系列三之單例模式(附案例原始碼)JavaScript設計模式單例原始碼