標籤:環境 多線程 就會 餓漢 instance 啟動 需要 網上 增加
單例設計模式是23種設計模式裡面最簡單的,但是要徹底理解單例,還是需要下一點功夫的。
單例一般會分為餓漢模式和懶漢模式
餓漢模式:
1 public class Singleton2 {3 private static Singleton singleton = new Singleton();4 5 public static Singleton getInstance()6 {7 return singleton;8 }9 }
但是在一些系統應用環境中,這個單例對象可能比較大,在類載入的時候就初始化對象會增加系統啟動壓力,還會對系統資源造成浪費。所以就有了懶漢模式,只有在第一次調用的時候才創界對象執行個體。
懶漢模式:
1 public class Singleton 2 { 3 private static Singleton singleton; 4 5 public static Singleton getInstance() 6 { 7 if (singleton == null) 8 { 9 singleton = new Singleton();10 }11 return singleton;12 }13 }
但是在多線程的環境中,以上內容就會有問題了。由於沒有同步,不同的線程可能同時進入if語句,然後分別建立了兩個執行個體,這時候Singleton就不再是單列了。
於是就有了雙重空判斷版本
public class Singleton{ private static Singleton1 singleton; public static Singleton1 getInstance() { if (singleton == null) { synchronized (Singleton1.class) { if (singleton == null) singleton = new Singleton(); } } return singleton; }}
網上很多資料說,這種寫法也不是安全執行緒的,singleton欄位必須定義為volatile才行。但實際上,以上代碼其實並沒有安全執行緒問題,因為Singleton1這個類並沒有狀態量。舉個例子,以下代碼才是非安全執行緒的:
public class WrongSingleton{ private static WrongSingleton singleton; public Object state = new Object(); public static WrongSingleton getInstance() { if (singleton == null) { synchronized (WrongSingleton.class) { if (singleton == null) singleton = new WrongSingleton(); } } return singleton; }}
由於cpu指令重排序的存在,我們無法確認singleton對象引用和state對象引用回寫到記憶體的順序,如果Singleton對象的引用已經由線程A回寫到了記憶體,而對象內部持有的state欄位還未完成回寫,那麼此時線程B調用getInstance()方法後,將得到一個錯誤的Singleton對象。其state引用為null。
要解決這個問題有兩個辦法:
一、把 private static WrongSingleton singleton; 改為 private static volatile Singleton singleton; 由於volatile 的可見度語意,所有對volatile變數的修改,都會立即回寫到主存,所以在Singleton建構函式返回前,state對象就已經回寫到主存了。
二、把state 對象定義為final欄位。由於final語意,final對象在建構函式完成後,其值的可見度對所有線程保持一致。
深入談談Java最簡單的單例設計模式