設計模式之單例模式
定義
單例模式從字面就看出來是要確保一個類中只有一個實例,字面上看不出來的是創建實例後,還要自行實例化並向整個系統提供這個實例。
動手試試
來寫一個簡單的單例類
public class Singleton{
private static Singleton singleton=null;
//限制產生多個對象
private Singleton(){
}
//通過該方法獲得實例對象
public static Singleton getSingleton(){
if(singleton==null)
singleton=new Singleton();
return singleton
}
}
這樣的單例類看上去沒什麼問題,實際上如果在低併發的情況下可能也不會出現問題。但如果併發量增加,則可能在內存中實現多個實例。如一個線程A執行到singleton=new Singleton(),但還沒有獲得對象,ei~這時候啊,還有一個線程B也在執行,執行到了singleton==null的判斷。線程A還沒有獲得對象啊,所以線程B的判斷條件也成立,結果呢,A獲得一個對象,B獲得一個對象,內存中有兩個對象!說好的單例呢!!
出現問題要想辦法解決
解決線程不安全,首先想到在get方法前加synchronized關鍵字。
public static synchronized Singleton getSingleton(){
if(singleton==null)
singleton=new Singleton();
return singleton
}
解決了線程問題,但是這樣synchronized鎖住了整個對象,這樣的用法,在性能上會有所下降,因爲每次調用getInstance(),都要對對象上鎖,事實上,只有在第一次創建對象的時候需要加鎖,之後就不需要了,所以,這個地方需要改進。我們改成下面這個:
public static Singleton getSingleton(){
if(singleton==null){
synchronized(singleton){
if(singleton==null)
singleton=new Singleton();
}
}
return singleton;
}
再來分析,有A、B兩個線程同時進入第一個if判斷,A首先進入synchronized代碼塊,由於singleton爲null,所以A執行singleton=new Singleton()。由於JVM內部的優化機制,JVM先畫出了一些分配給Singleton實例的空白內存,並賦值給instance成員(注意此時JVM沒有開始初始化這個實例),然後A離開了synchronized塊。B進入synchronized塊,由於instance此時不是null,因此它馬上離開了synchronized塊並將結果返回給調用該方法的程序。此時B線程打算使用Singleton實例,卻發現它沒有被初始化,於是錯誤發生了。所以程序還是有可能發生錯誤,其實程序在運行過程是很複雜的,從這點我們就可以看出,尤其是在寫多線程環境下的程序更有難度,有挑戰性。我們對該程序做進一步優化:
public class Singleton{
private static final Singleton singleton=new Singleton();
//限制產生多個對象
private Singleton(){
}
//通過該方法獲得實例對象
public static Singleton getSingleton(){
return singleton
}
}
JVM內部的機制能保證一個類被加載的時候,這個類的加載過程是線程互斥的。這樣當第一次調用getSingleton的時候,JVM能幫助我們保證singleton只能被創建一次,並且會保證把賦值給singleton的內存初始化完畢,這樣我們就不用擔心上面的問題。同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題。這樣我們暫時總結一個完美的單例模式。