實現單例模式的方法的8種之一:雙重檢查方式實現
public class SingleTonSample { private static SingleTonSample singleTonSample; private SingleTonSample(){} public static SingleTonSample getInstance(){ if (singleTonSample==null){ synchronized (SingleTonSample.class){ if (singleTonSample==null){ singleTonSample= new SingleTonSample(); } } } return singleTonSample; } }
乍一看這段代碼是沒有問題的,但是如果多線程操作的時候下面這段代碼是會出現重排序問題的
singleTonSample= new SingleTonSample();
爲什麼呢?
創建一個對象其實是有三個步驟的
第一步:在內存中給singleTonSample分配內存空間
第二步:調用SingleTonSample的構造方法並執行構造方法中的代碼
第三步: singleTonSample指向分配的內存空間
可見,我們創建一個對象並不是一個原子操作,所以如果在多線程來操作的時候出現重排序後果照樣很嚴重
舉個栗子:第一步如果Thread1進入這段代碼
if (singleTonSample==null){ singleTonSample= new SingleTonSample(); }
創建對象這個過程中發生了重排序,導致第三步在第二步執行之前執行了
第一步:在內存中給singleTonSample分配內存空間
第三步: singleTonSample指向分配的內存空間
第二步:調用SingleTonSample的構造方法並執行構造方法中的代碼
那麼構造函數中的代碼並未執行(在構造函數中可能有很多業務代碼,比如連接數據庫,初始化數據什麼的...)就給singleTonSample進行了賦值。恰巧第二個線Thread2程搶到了鎖 這時判斷singleTonSample是不爲空的,便把Thread1的執行的結果給拿到,那麼這是有很大問題的,它那拿到的其實只是一個空殼子,同時Thread2使用構造方法裏面的屬性的時候便會報空指針錯誤 這是很嚴重的問題。
怎麼解決呢?
其實很簡單只需要加一個volatile關鍵字即可,因爲volatile有禁止重排序的功能,正確方法如下
private volatile static SingleTonSample singleTonSample;
至此一個完美的雙重檢查方法實現的單例模式便寫完:
public class SingleTonSample { private volatile static SingleTonSample singleTonSample; private SingleTonSample(){} public static SingleTonSample getInstance(){ if (singleTonSample==null){ synchronized (SingleTonSample.class){ if (singleTonSample==null){ singleTonSample= new SingleTonSample(); } } } return singleTonSample; } }