單例模式由于只創(chuàng)建了唯一對象可以避免資源的多重占用,減少內(nèi)存的開銷,對于經(jīng)常性使用對象的類來說,單例是一個不錯的選擇,使用場景,比如:文件操作、共享資源等等。
1、餓漢單例模式
這是最為簡單的也是最基本的單例模式,相信大家都寫過!先上代碼:
public class Single{
private static final Single instance = new Single();
private Single(){}
public static Single getInstance(){
return instance;
}
}
由于把構(gòu)造函數(shù)變?yōu)榱藀rivate,所以要想獲得該類的實例需要通過getInstance(),而不是手動去new一個,這僅僅是最為簡單粗暴的單例模式。
2、懶漢單例模式
public class Single{
private static Single instance = null;
private Single(){}
public static synchronized Single getInstance(){
if (instance == null){
instance = new Single();
}
return instance;
}
}
懶漢單例模式看起來和餓漢單例模式差不多,就多了一個 synchronized 關鍵字,也就是說該模式是同步的方法單例。在 getInstanc()方法中,可以清楚知道不管該類對象是否已經(jīng)實例了(實際上第一次調(diào)用的時候只new了一次),都會進行同步,這樣確實每次都同步確實耗費了不必要的資源和內(nèi)存,而且在加載的時候都會事先 synchronized,所以會比較耗時間,所以不太建議使用。
3、Double Check Lock(DCL)
public class Single{
private static Single instance = null;
private Single(){}
public static Single getInstance(){
if (instance == null){
synchronized (Single.class){
if (instance == null){
instance = new Single();
}
}
}
return instance;
}
}
在代碼中可以看到,在調(diào)用 getInstance() 方法的時候會檢查類的實例是否為空,true則直接返回該實例,false則會 synchronized (利用Single.class來對本類對象同步),如果為null則會new一個對象,否則直接返回。這意思很清楚,這里有兩次判斷是否為null的步驟,第一步判斷是為了避免不必要的同步,而第二步則是同步檢查。
??我們知道在new,即instance = new Single()的時候,一般會有三個步驟:1、分配內(nèi)存;2、調(diào)用構(gòu)造函數(shù)并初始化成員字段;3、為對象指向分配好的空間。而Java是允許處理器亂序執(zhí)行的,還有JDK版本原因,很多時候這三個步驟很可能不是按照順序執(zhí)行的,
所以在多線程的操作下,如果在A線程中執(zhí)行某一步的時候,B線程也調(diào)用了該方法,而這時候A線程剛剛好分配內(nèi)存成功(假設第一個調(diào)用)但未調(diào)用構(gòu)造函數(shù)創(chuàng)建對象,所以這時候B在檢查的時候?qū)ο笠呀?jīng)非空了(因為已經(jīng)指向了內(nèi)存),所以B線程會直接使用對象instance,很顯然,由于還沒調(diào)用構(gòu)造函數(shù),所以B線程使用的時候會出問題。這叫DCL失效。雖然說這是很小的概率問題,但還是會長期隱藏著問題的。不過從JDK1.5之后,sun注意了這個問題,把這個bug修改了過來,只要 如此聲明:private volatile static Single instance = null 就可以保證instance 是從主內(nèi)存取出來的,雖然volatile 會影響性能,但為了DCL有效就值得。
??總的來說DCL是餓漢、懶漢單例模式的結(jié)合,盡管存在bug,但還是sun已修改了,所以比較建議這種寫法。
4 、內(nèi)部靜態(tài)類單例模式
??利用靜態(tài)內(nèi)部類的特性來返回對象(static關鍵字不用多講了吧)
public class Single {
private Single (){}
public static Single getInstance(){
return SingleHolder.instance;
}
private static class SingleHolder{
private static final Single instance = new Single();
}
}
??這不僅僅首次調(diào)用才初始化,而且線程安全,也能保證對象的唯一性,所以這也是推薦的寫法。
5、枚舉單例模式
public enum SingleEnum {
INSTANCE;
}
在Java中,枚舉類型是默認線程安全的,在任何情況下都能保證唯一性,而且寫法最為簡單。大家可以嘗試一下的
6、容器單例模式
public class SingleCollect {
private static Map<String, Object> objectMap = new HashMap<>();
private SingleCollect(){}
/**
* 根據(jù)類名把實例通過Map保存起來
* @param key 類名
* @param instance 類的實例對象
*/
public static void registInatance(String key, Object instance){
if (!objectMap.containsKey(key)){
objectMap.put(key, instance);
}
}
/**
* 根據(jù)類名來尋找對應的對象實例
* @param key
* @return
*/
public static Object getInstance(String key){
return objectMap.get(key);
}
}
??利用Map把類的對象實例保存起來,在需要的時候根據(jù)類名key獲取即可。
總結(jié)
??通過幾種單例模式,核心思想無非是把構(gòu)造函數(shù)私有化,然后利用 public static修飾符來獲取對象實例,并且保證線程安全?。?!值得注意的時候,我們還需要考慮這么一種情況:反序列化。我們知道可以通過序列化把對象實例寫進磁盤,然后再讀回來。而反序列化依然可以通過別的途徑去重新創(chuàng)建一個新的對象實例,即便是私有的構(gòu)造函數(shù)!上述的幾種模式就只有枚舉單例模式可以避免。不過,在實際開發(fā)中,我們需要結(jié)合項目需要,而不是一味地直接使用枚舉單例模式,靈活使用單例模式才是正道。
??上文如有不對或者不妥之處,大家記得留言指出哈。一起進步才比較爽啊?。?!and then 后續(xù)我會繼續(xù)寫一系列關于設計模式的,請大家靜候!