單例模式由于只創(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 關(guān)鍵字,也就是說該模式是同步的方法單例官疲。在 getInstanc()方法中袱结,可以清楚知道不管該類對象是否已經(jīng)實例了(實際上第一次調(diào)用的時候只new了一次),都會進(jìn)行同步途凫,這樣確實每次都同步確實耗費(fèi)了不必要的資源和內(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)存成功(假設(shè)第一個調(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關(guān)鍵字不用多講了吧)
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中砾医,枚舉類型是默認(rèn)線程安全的拿撩,在任何情況下都能保證唯一性,而且寫法最為簡單如蚜。大家可以嘗試一下的
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ù)類名來尋找對應(yīng)的對象實例
* @param key
* @return
*/
public static Object getInstance(String key){
return objectMap.get(key);
}
}
??利用Map把類的對象實例保存起來,在需要的時候根據(jù)類名key獲取即可错邦。
總結(jié)
??通過幾種單例模式探赫,核心思想無非是把構(gòu)造函數(shù)私有化,然后利用 public static修飾符來獲取對象實例撬呢,并且保證線程安全B追汀!魂拦!值得注意的時候毛仪,我們還需要考慮這么一種情況:反序列化。我們知道可以通過序列化把對象實例寫進(jìn)磁盤晨另,然后再讀回來潭千。而反序列化依然可以通過別的途徑去重新創(chuàng)建一個新的對象實例,即便是私有的構(gòu)造函數(shù)借尿!上述的幾種模式就只有枚舉單例模式可以避免刨晴。不過,在實際開發(fā)中路翻,我們需要結(jié)合項目需要狈癞,而不是一味地直接使用枚舉單例模式,靈活使用單例模式才是正道茂契。
??上文如有不對或者不妥之處蝶桶,大家記得留言指出哈。一起進(jìn)步才比較爽暗粢薄U媸!厌小!and then 后續(xù)我會繼續(xù)寫一系列關(guān)于設(shè)計模式的恢共,請大家靜候!