前言
單例模式是一種比較簡單也比較常用的模式德澈。這種模式涉及到一個單一的類歇攻,該類負(fù)責(zé)創(chuàng)建自己的實例,同時也確保只有單個實例被創(chuàng)建梆造,并有接口對外提供這個實例缴守。一般單例模式多用于含有多個比較耗費資源的全局變量的工具類中。比如在一個Android應(yīng)用中镇辉,應(yīng)該只有一個ImageLoader實例屡穗,這個ImageLoader中含有線程池、緩存系統(tǒng)忽肛、網(wǎng)絡(luò)請求等村砂,創(chuàng)建多個的話很耗資源,就很沒必要屹逛。下面講幾種比較常見的單例模式:
1.懶漢式
public class SingleTon {
private static SingleTon instance ;
private SingleTon(){}
public static sysnchronized SingleTon getInstance() {
if (instance == null) {
instance = new SingleTon() ;
}
return instance ;
}
}
SingleTon 類中先定義了一個靜態(tài)對象础废,并且在第一次調(diào)用 getInstance 時初始化并返回該對象,后面每一次調(diào)用都會做一次判斷罕模。
懶漢式和餓漢式很容易記反有沒有评腺,可能都是褒義詞的“近義詞”吧。之前在看《Thinking In Java》一書中有提到 惰性初始化(Lazy Loading) 一詞淑掌,就是讓對象在將要使用之前的位置初始化蒿讥,避免了內(nèi)存浪費。2個放一起記就好記了,在第一次使用該單例對象的時候初始化的就是懶漢式芋绸。
懶漢式是通過來 synchronized 來加鎖保證線程安全的媒殉,但是鎖會影響效率,而且絕大多數(shù)情況下是不需要同步的侥钳。
2.餓漢式
public class SingleTon {
private static final SingleTon instance = new SingleTon() ;
private SingleTon(){}
public static sysnchronized SingleTon getInstance() {
return instance ;
}
}
這種方式基于 ClassLoader 避免了多線程同步的問題适袜,沒有加鎖,執(zhí)行效率會提高舷夺。但是instance 在類裝載的時候就實例化苦酱,浪費內(nèi)存,很容易產(chǎn)生垃圾對象给猾。關(guān)于類裝載看一下這篇文章:單例模式之類的加載 疫萤。
3.雙重檢查鎖(DLC, Double Check Lock)
public class SingleTon {
private static final SingleTon instance = null ;
private SingleTon(){}
public static sysnchronized SingleTon getInstance() {
if(instance == null) {
sysnchronized(SingleTon.class) {
if(instance == null) {
instance = new SingleTon() ;
}
}
}
return instance ;
}
}
我的 synchronized / Lock+Volatile 這篇文章有關(guān)于java中3種鎖的介紹敢伸,感興趣的請移步看一下扯饶。
關(guān)于這里為什么要用到2個 if(instance == null)來判斷,是因為 instance = new SingleTon() 這一句不是一個原子操作池颈,用偽代碼來表示分為三步:
inst = allocat() ; // 分配內(nèi)存
constructor(inst) ; // 執(zhí)行構(gòu)造函數(shù)
instance = inst ; // 賦值
Java編譯器允許處理器亂序執(zhí)行尾序,也就是可能會有指令沖排序發(fā)生,也就是說上面的第2躯砰、3步執(zhí)行的順序是無法保證的每币。也就是說執(zhí)行順序可能是1-2-3也可能是1-3-2.如果是1-3-2的話,當(dāng)線程A執(zhí)行了1-3時琢歇,instance已經(jīng)不為 null 了兰怠,但是還沒有調(diào)用構(gòu)造函數(shù)初始化,這時候切換到線程B李茫,B就直接取走了instance揭保,在使用的時候就會出錯了。當(dāng)然 volatile 關(guān)鍵字也能保證原子操作不亂序執(zhí)行魄宏,我上面的文章中有說明秸侣。
4.登記式(靜態(tài)內(nèi)部類)
public class SingleTon {
private SingleTon(){}
public static sysnchronized SingleTon getInstance() {
return SingleTonHolder.instance ;
}
private static class SingleTonHolder {
private static final SingleTon instance = new SingleTon() ;
}
}
這種方式能達到雙檢鎖方式一樣的功效,但實現(xiàn)更簡單宠互。對靜態(tài)域使用惰性初始化塔次,應(yīng)使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態(tài)域的情況名秀,雙檢鎖方式可在實例域需要延遲初始化時使用励负。
這種方式同樣利用了 classloder 機制來保證初始化 instance 時只有一個線程,它跟第 DCL 種方式不同的是:第 DCL 種方式只要 Singleton 類被裝載了匕得,那么 instance 就會被實例化(沒有達到 lazy loading 效果)继榆,而這種方式是 Singleton 類被裝載了巾表,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用略吨,只有顯示通過調(diào)用 getInstance 方法時集币,才會顯示裝載 SingletonHolder 類,從而實例化 instance翠忠。想象一下鞠苟,如果實例化 instance 很消耗資源,所以想讓它延遲加載秽之,另外一方面当娱,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載考榨,那么這個時候?qū)嵗?instance 顯然是不合適的跨细。這個時候,這種方式相比第 DCL 種方式就顯得很合理河质。
5.枚舉單例
public class SingleTon {
INSTANCE ;
public void whateverMethod(){ }
}
在上述的4中單例實現(xiàn)中冀惭,在反序列化的時候都可能會創(chuàng)建一個新的實例。當(dāng)然也可以通過鉤子函數(shù) readResolve() 來阻止反序列化的時候重新生成對象掀鹅。但是枚舉最大的有點就是簡單散休,自動支持序列化機制,而且能保證絕對的單例乐尊。但是枚舉是在Java1.5之后才加入的新特性戚丸。
總結(jié):
不管以哪種形式實現(xiàn)單例模式,核心原理都是 將構(gòu)造函數(shù)私有化科吭,并且通過靜態(tài)方法來獲取一個唯一的實例,在獲取的過程中必須保證線程安全猴鲫、防止反序列化重新生成實例对人。一般情況下,不建議使用懶漢方式拂共,建議使用餓漢方式牺弄。只有在要明確實現(xiàn) lazy loading 效果時,才會使用登記式宜狐。如果涉及到反序列化創(chuàng)建對象時势告,可以使用枚舉方式。
參考書籍:
《Android源碼設(shè)計模式解析與實戰(zhàn)》