第 6 章 單例模式與多線程
本章主要內容
如何使單例模式遇到多線程是安全的、正確的。
6.1 立即加載 / “餓漢模式”
什麼是立即加載?立即加載就是使用類的時候已經將對象創建完畢,常見的實現辦法就是直接 new 實例化。而立即加載也稱爲“餓漢模式”。
6.2 延遲加載 / “懶漢模式”
什麼是延遲加載?延遲加載就是在調用 get() 方法時實例才被創建,常見的實現辦法就是在 get() 方法中進行 new 實例化。延遲加載也稱爲“懶漢模式”。
1. 延遲加載 / “懶漢模式” 解析
延遲加載 / “懶漢模式” 是在調用方法時實例才被創建。
這種模式如果是在多線程的環境中,就會出現取出多個實例的情況,與單例模式的初衷是相背離的。
2. 延遲加載 / “懶漢模式” 的缺點
“延遲加載”在多線程的環境中,根本不能實現保持單例的狀態。
3. 延遲加載 / “懶漢模式”的解決方案
(1)聲明 synchronized 關鍵字
既然多個線程可以同時進入 getInstance() 方法,那麼只需要對 getInstance() 方法聲明 synchronized 關鍵字即可。
爲 getInstance() 方法加入同步 synchronized 關鍵字得到相同實例的對象,但此種方法的運行效率非常低下,是同步運行的,下一個線程想要取得對象,則必須等上一個線程釋放鎖之後,纔可以繼續執行。
(2)嘗試同步代碼塊
同步方法是對方法的整體進行持鎖,這對運行效率來講是不利的。
同步 synchronized 語句塊得到相同實例的對象,但此種方法的運行效率也是非常低的,和 synchronized 同步方法一樣是同步運行的。
(3)針對某些重要的代碼進行單獨的同步
同步代碼塊可以針對某些重要的代碼進行單獨的同步,而其他的代碼則不需要同步。這樣在運行時,效率完全可以得到大幅提升。
此方法使用同步 synchronized 語句塊,只對實例化對象的關鍵代碼進行同步,從語句的結構上來講,運行的效率的確得到了提升。但如果是遇到多線程的情況下還是無法解決得到同一個實例對象的結果。
(4)使用 DCL 雙檢查鎖機制
使用 DCL 雙檢查鎖機制來實現多線程環境中的延遲加載單例設計模式。
DCL 雙檢查模式就是一方面使用 synchronized 語句款,只對實例化對象的關鍵代碼進行同步,另一方面對單例對象使用 volatile 關鍵字。
使用雙重檢查鎖功能,成功地解決了“懶漢模式”遇到多線程的問題。DCL 也是大多數多線程結合單例模式使用的解決方案。
6.3 使用靜態內置類實現單例模式
DCL 可以解決多線程單例模式的非線程安全問題。當然,使用其他的辦法也能達到同樣的效果。
public class MyObject {
//內部類方式
public static class MyObjectHandler {
private static MyObject myobject = new MyObject();
}
private MyObject() {
}
public static MyObjectgetInstance() {
return MyObjectHandler.myObject;
}
}
6.4 序列化與反序列化的單例模式實現
靜態內置類可以達到線程安全問題,但如果遇到序列化對象時,使用默認的方式運行得到的結果還是多例的(單例類 implements Serializable)。
文件讀和寫獲取的對象實例不同,解決辦法就是在反序列中使用 readResolve() 方法。
6.5 使用 static 代碼塊實現單例模式
靜態代碼塊中的代碼在使用類的時候就已經執行了,所以可以應用靜態代碼塊的這個特性來實現單例設計模式。
static {
instance = new MyObject();
}
6.6 使用 enum 枚舉數據類型實現單例模式
枚舉 enum 和靜態代碼塊的特性相似,在使用枚舉類時,構造方法會被自動調用,也可以應用其這個特性實現單例設計模式。
public enum MyObject{
...
private MyObject(){
//單例初始化
}
}
6.7 完善使用 enum 枚舉實現單例模式
前面一節將枚舉類進行暴露,違反了“職責單一原則”。
public class MyObject{
public enum MyEnumSingletion{
...
private MyEnumSingletion(){
//單例初始化
}
}
}
6.8 本章總結
本章使用若干案例來闡述單例模式與多線程結合時遇到的情況與解決方法。