“單例模式” 之雙重檢查實現方法的弊端與優化

實現單例模式的方法的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;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章