【劍指offer Java】面試題2:實現Singleton模式
阿新 • • 發佈:2019-01-31
題目:設計一個類,我們只能生成該類的一個例項。
//餓漢式
public static class Singleton01{
//預先初始化static變數
private final static Singleton01 INSTANCE = new Singleton01();
private Singleton01(){
}
public static Singleton01 getInstance(){
return INSTANCE;
}
}
1.優點:執行緒安全,因為static類在類載入時只初始化一次,保證執行緒安全
2.缺點:一來就建立例項化物件,不管後面這個物件用不用的到,所以缺點是若構造的例項很大,而構造完又不用,會導致資源的浪費
//懶漢式,無同步鎖
public static class Singleton02{
private static Singleton02 instance = null;
//私有化構造方法,保證外部類不能通過此構造器來例項化
private Singleton02(){
}
//當多執行緒同時呼叫getInstance()時,可能創造多個例項物件,並且將多個例項物件賦值給例項變數instance
public static Singleton02 getInstance(){
if(instance == null)
instance = new Singleton02();
return instance;
}
}
- 優點:懶漢式只有在用到的時候(即呼叫getInstance()方法)才建立例項化物件,避免資源浪費
- 缺點:1、若初始化非常耗時時,會造成資源浪費,2、非執行緒安全,多執行緒可能趙成多個例項物件被初始化
//懶漢式,同步鎖
public static class Singleton03{
private static Singleton03 instance = null;
private Singleton03(){
}
/*
* 獲得單例物件例項,同步互斥訪問實現執行緒安全
*/
public static synchronized Singleton03 getInstance(){
if(instance == null)
instance = new Singleton03();
return instance;
}
}
優點:1、當要使用時才例項化單例,避免資源浪費。2、執行緒安全
缺點:1、若初始化例項時耗時,則會造成效能降低。2、每次呼叫getInstance()都獲得同步鎖,效能消耗(但卻無法避免)
針對Singleton03出現的每次呼叫getInstance()都獲得同步鎖而出現的效能問題,那麼將synchronized關鍵字放到getInstance()呼叫函式裡面,如下面Singleton04()程式碼所示。
但隨之而來的問題是:多執行緒下同時呼叫getInstance(),這時instance都為空,多執行緒都進入了synchronized塊建立例項,就和前面的Singleton03一樣了
//懶漢式,試圖將同步鎖放在判斷是否為單例(instance == null)之後
public static class Singleton04{
private static Singleton04 instance = null;
private Singleton04(){
}
public static Singleton04 getInstance(){
if(instance == null){
synchronized (Singleton04.class){
instance = new Singleton04();
}
}
return instance;
}
}
為了解決Singleton03()、Singleton04()帶來的可能建立多例項物件問題,所以用雙重校驗鎖。
之所以有兩個(instance == null)判斷是因為考慮了多執行緒問題(這也是所謂的雙重校驗鎖),當有多個執行緒同時呼叫getInstance()的時候,這些執行緒都能進入第一個if(instance == null)判斷,然而只有一個執行緒能進入第二個if(instance == null)來建立物件。所以,若沒有第二個(instance == null)判斷,那麼多個執行緒將會建立多個物件。如Singleton05()所示:
//懶漢式,雙重校驗鎖
public static class Singleton05{
private volatile static Singleton05 instance = null;
private Singleton05(){
}
public static Singleton05 getInstance(){
if(instance == null){
synchronized (Singleton05.class){
if(instance == null)
instance = new Singleton05();
}
}
return instance;
}
}
我們知道,JVM的記憶體模型中有一個“無序寫”(out-of-order writes)機制,而雙重校驗鎖中instance = new Singleton05()中做了兩件事:
1、呼叫構造方法,建立例項化物件。
2、將例項化物件賦值給引用變數instance
因為“無序寫”機制,所以步驟1、2可能是亂序的,因此Singleton05中多執行緒可能因為第一個執行緒instance = new Singleton05()先賦值了instance,但是例項化物件卻沒有建立因此下一個執行緒進入(instance == null)判斷時為false,因此直接返回了instance。但是這個instance是沒有經過例項化物件賦值而是預設值的,所以是錯誤的。而這時第一個執行緒才開始例項化物件,並且把正確的例項化物件賦值給引用instance。 為了解決JVM的“無序寫”問題,用臨時變數tmp
PS:其實就是兩次(instance == null)判斷後面需要synchronized同步鎖來進行執行緒互斥
//懶漢式,雙重校驗鎖
public static class Singleton06{
private static Singleton06 instance = null;
private Singleton06(){
}
public static Singleton06 getInstance(){
if(instance == null){
synchronized(Singleton06.class){
Singleton06 tmp = instance;
if(tmp == null){
synchronized(Singleton06.class){
tmp = new Singleton06();
}
instance = tmp;
}
}
}
return instance;
}
}
//靜態方法塊只調用一次,省去了if(instance == null)的比較
public static class Singleton07{
private static Singleton07 instance = null;
static{
instance = new Singleton07();
}
private Singleton07(){
}
public static Singleton07 getInstance(){
return instance;
}
}
使用靜態內部類SingletonHolder來實現懶漢式單例,因為JVM中內部類只有在getInstance()方法第一次被呼叫時才被載入,即實現了懶漢式(lazy).而static內部類只會被載入一次,因此載入過程是執行緒安全的
//懶漢式,靜態內部類實現
public static class Singleton08{
private final static class SingletonHolder{
private static final Singleton08 INSTANCE = new Singleton08();
}
private Singleton08(){
}
public static Singleton08 getInstance(){
return SingletonHolder.INSTANCE;
}
}
/*
* 總的來說,就是要考慮是否是懶漢式單例?是否執行緒安全?是否效能最好?
* 按照程式碼的邏輯來考慮,就是是否是懶漢式單例【即(instance == null)】?
* 然後考慮是否執行緒安全【即synchronized的使用】?
* 最後考慮效能如何【即sychronized關鍵字使用是在getInstance()還是在
* (instance == null)之中】
*/