一、前言
如何使單例模式遇到多執行緒是安全的、正確的?
我們在學習設計模式的時候知道單例模式有懶漢式和餓漢式之分。簡單來說,餓漢式就是在使用類的時候已經將物件建立完畢,懶漢式就是在真正呼叫的時候進行例項化操作。
二、餓漢式+多執行緒
public class myobject
public static myobject getinstance()
}
自定義執行緒類:
public class mythread extends thread
}
執行類:
public class run
}
程式執行後的結果如下所示:
hashcode是同乙個值,說明物件是同乙個。也就是說餓漢式單例模式在多執行緒環境下是執行緒安全的。
三、懶漢式+多執行緒
public class myobject
public static myobject getinstance()
} catch (exception e)
return myobject;
}}
自定義執行緒類:
public class mythread extends thread
}
測試類:
public class run
}
程式執行後的結果如下:
3種hashcode,說明建立出了3個物件,並不是單例的。懶漢模式在多執行緒環境下是「非執行緒安全」。這是為何?
因為建立例項物件的那部分**沒有加synchronized或lock。三個執行緒都進入了建立例項物件的**段getinstance。
三、懶漢式+多執行緒 非「單例解決方案嘗試
1、synchronized同步方法
既然多個執行緒可以同時進入getinstance()方法,那麼只需要對getinstance()方法宣告synchronized關鍵字即可。在myobject的getinstance()方法前加synchronized關鍵字。最終列印的三個hashcode是一樣一樣的。實現了多執行緒環境下,懶漢模式的正確性、安全性。但是此種方法的執行效率非常低下,因為是同步的,乙個執行緒釋放鎖之後,下乙個執行緒繼續執行。
2、synchronized同步**塊
同步方法是對方法整體加鎖,效率不高,我們可以通過減少鎖的粒度,也就是使用synchronized同步**塊。如下面**所示:
public class myobject
/*synchronized*/
public static myobject getinstance()
}} catch (exception e)
return myobject;
}}
這樣做能保證最終執行結果正確,但getinstance方法中的全部**都是同步的了,這樣做會降低執行效率,和對getinstance方法加synchronized的效率幾乎一樣。
3、重要**同步**塊
public class myobject
/*synchronized*/
public static myobject getinstance()
}} catch (exception e)
return myobject;
}}
最終輸出結果如下:
這種做法在多執行緒環境下還是無法解決得到同乙個例項物件的結果。
4、雙重鎖定
package singleton_3;
public class myobject
//使用雙重鎖定(double-check locking)解決問題,既保證了不需要同步**的非同步執行性,
//又保證了單例的效果
public static myobject getinstance()}}
} catch (exception e)
return myobject;
}}
使用雙重鎖定功能,成功地解決了在多執行緒環境下「懶漢模式」的「非執行緒安全」問題。
那麼為什麼外面已經判斷myobject例項是否存在,為什麼在lock裡面還需要做一次myobject例項是否存在的判斷呢?
如果myobject已經存在,則直接返回,這沒有問題。當instance為null,並且同時有3個執行緒呼叫getinstance()方法時,它們都可以通過第一重myobject==null的判斷,然後由於lock機制,這三個執行緒只有乙個進入,另外2個在外排隊等候,必須第乙個執行緒走完同步**塊之後,第二個執行緒才進入同步**塊,此時判斷instance==null,為false,直接返回myobject例項。就不會再建立新的例項啦。第二個監測myobject==null一定要在同步**塊中。
5、使用volatile關鍵字
上面我們雙重鎖定表面上看沒有什麼問題了,但是根本上說還是有一定問題的。
問題出現在new操作上。我們以為的new操作應該是3個步驟。(美團面試題:解析一下物件的建立過程(半初始化))
① 分配一塊記憶體m,成員變數是預設值。
② 在記憶體m上初始化物件,給成員變數設定初始值。
③ 把m的位址賦給myobject變數。
但實際上優化後的執行路徑可能如下所示:
① 分配一塊記憶體m。
② 把m的位址賦給myobject變數。
③ 在記憶體m上初始化物件。
具體詳情請參考這篇部落格。使用雙重鎖定保證懶漢式在多執行緒環境下的安全性,還需要再靜態成員變數myobject之前增加修飾符volatile,被volatile修飾的成員變數一是能保證記憶體模型的可見性。也就是說當乙個執行緒修改了成員變數的值,會通知到其他執行緒。二是能保證多執行緒環境下重排序被禁止。
三、使用靜態內建類實現單例模式
public class myobject
//靜態內部類方式
private static class myobjecthandler
public static myobject getinstance()
}
最終輸出結果3個hashcode是一樣的。
四、使用靜態**塊實現單例模式
public class myobject
static
public static myobject getinstance()
}
最終輸出結果是3個相同的hashcode。
五、總結
單例模式分為懶漢式和餓漢式,餓漢式在多執行緒環境下是執行緒安全的;懶漢式在多執行緒環境下 是「非執行緒安全」的,可以通過synchronized同步方法和「雙重檢測」機制來保證懶漢式在多執行緒環境下的執行緒安全性。靜態內部類實現單例模式和靜態**塊從廣義上說都是餓漢式的。
多執行緒 多執行緒 單例設計模式
多執行緒之 單例設計模式 餓漢式 多執行緒安全 1 餓漢式 class single static single getinstance public void show class a implements runnable class test catch interruptedexceptio...
單例模式與多執行緒
立即載入就是使用類的時候已經將物件建立完畢,常見的實現辦法就是直接new例項化,在呼叫方法前例項已經被建立了.利用getinstance 獲得乙個物件.延遲載入就是在呼叫get 方法時例項才被建立,常見的實現辦法就是在get 方法中進行new例項化,在呼叫方法時例項才被建立.利用get 獲得乙個物件...
單例模式與多執行緒
程式 上面的就是典型的 餓漢模式 就是急不可耐,一上來就初始化物件。那能不能使用的時候才例項化物件呢?也就是希望延遲載入,這就是所謂的 懶漢模式 程式 如下 在併發情況下,多個執行緒同時 抵達 if判斷這塊,那麼勢必物件會被多次new。那麼 懶漢模式 下,如何保證併發呢?簡單來說,我們可以在geti...