概述
單例模式(Singleton Pattern):確保某一個(gè)類只有一個(gè)實(shí)例誓篱,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,這個(gè)類稱為單例類托猩,它提供全局訪問的方法肉微。單例模式是一種對(duì)象創(chuàng)建型模式。
單例模式有三個(gè)要點(diǎn):
- 某個(gè)類只能有一個(gè)實(shí)例
- 它必須自行創(chuàng)建這個(gè)實(shí)例
- 是它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例酬核。
單例模式是結(jié)構(gòu)最簡(jiǎn)單的設(shè)計(jì)模式一蜜另,在它的核心結(jié)構(gòu)中只包含一個(gè)被稱為單例類的特殊類。單例模式結(jié)構(gòu)如圖所示:
單例模式結(jié)構(gòu)圖中只包含一個(gè)單例角色:
- Singleton(單例):在單例類的內(nèi)部實(shí)現(xiàn)只生成一個(gè)實(shí)例嫡意,同時(shí)它提供一個(gè)靜態(tài)的getInstance()工廠方法举瑰,讓客戶可以訪問它的唯一實(shí)例;為了防止在外部對(duì)其實(shí)例化蔬螟,將其構(gòu)造函數(shù)設(shè)計(jì)為私有此迅;在單例類內(nèi)部定義了一個(gè)Singleton類型的靜態(tài)對(duì)象,作為外部共享的唯一實(shí)例旧巾。
餓漢式單例與懶漢式單例
餓漢式單例類
餓漢式單例類是實(shí)現(xiàn)起來最簡(jiǎn)單的單例類耸序,餓漢式單例類結(jié)構(gòu)圖如圖所示
從圖中可以看出,由于在定義靜態(tài)變量的時(shí)候?qū)嵗瘑卫惵承桑虼嗽陬惣虞d的時(shí)候就已經(jīng)創(chuàng)建了單例對(duì)象坎怪,代碼如下所示:
class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
當(dāng)類被加載時(shí),靜態(tài)變量instance會(huì)被初始化廓握,此時(shí)類的私有構(gòu)造函數(shù)會(huì)被調(diào)用搅窿,單例類的唯一實(shí)例將被創(chuàng)建。如果使用餓漢式單例來實(shí)現(xiàn)單例模式的設(shè)計(jì)隙券,則不會(huì)出現(xiàn)創(chuàng)建多個(gè)單例對(duì)象的情況男应,可確保單例對(duì)象的唯一性。
懶漢式單例類與線程鎖定
還有一種經(jīng)典的懶漢式單例是尔。懶漢式單例類結(jié)構(gòu)圖如圖所示:
從圖中可以看出,懶漢式單例在第一次調(diào)用getInstance()方法時(shí)實(shí)例化开仰,在類加載時(shí)并不自行實(shí)例化拟枚,這種技術(shù)又稱為延遲加載(Lazy Load)技術(shù)薪铜,即需要的時(shí)候再加載實(shí)例,為了避免多個(gè)線程同時(shí)調(diào)用getInstance()方法恩溅,我們可以使用關(guān)鍵字synchronized隔箍,代碼如下所示:
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
該懶漢式單例類在getInstance()方法前面增加了關(guān)鍵字synchronized進(jìn)行線程鎖,以處理多個(gè)線程同時(shí)訪問的問題脚乡。但是蜒滩,上述代碼雖然解決了線程安全問題,但是每次調(diào)用getInstance()時(shí)都需要進(jìn)行線程鎖定判斷奶稠,在多線程高并發(fā)訪問環(huán)境中俯艰,將會(huì)導(dǎo)致系統(tǒng)性能大大降低。
如何既解決線程安全問題又不影響系統(tǒng)性能呢锌订?我們繼續(xù)對(duì)懶漢式單例進(jìn)行改進(jìn)竹握。事實(shí)上,我們無須對(duì)整個(gè)getInstance()方法進(jìn)行鎖定辆飘,只需對(duì)其中的代碼“instance = new LazySingleton();”進(jìn)行鎖定即可啦辐。因此getInstance()方法可以進(jìn)行如下改進(jìn)
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
如果使用以上代碼來實(shí)現(xiàn)單例,還是會(huì)存在單例對(duì)象不唯一蜈项。
假如在某一瞬間線程A和線程B都在調(diào)用getInstance()方法芹关,此時(shí)instance對(duì)象為null值,均能通過instance == null的判斷紧卒。由于實(shí)現(xiàn)了synchronized加鎖機(jī)制侥衬,線程A進(jìn)入synchronized鎖定的代碼中執(zhí)行實(shí)例創(chuàng)建代碼,線程B處于排隊(duì)等待狀態(tài)常侦,必須等待線程A執(zhí)行完畢后才可以進(jìn)入synchronized鎖定代碼浇冰。但當(dāng)A執(zhí)行完畢時(shí),線程B并不知道實(shí)例已經(jīng)創(chuàng)建聋亡,將繼續(xù)創(chuàng)建新的實(shí)例肘习,導(dǎo)致產(chǎn)生多個(gè)單例對(duì)象,違背單例模式的設(shè)計(jì)思想坡倔,因此需要進(jìn)行進(jìn)一步改進(jìn)漂佩,在synchronized中再進(jìn)行一次(instance == null)判斷,這種方式稱為雙重檢查鎖定(Double-Check Locking)罪塔。使用雙重檢查鎖定實(shí)現(xiàn)的懶漢式單例類完整代碼如下所示:
class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判斷
if (instance == null) {
//鎖定代碼塊
synchronized (LazySingleton.class) {
//第二重判斷
if (instance == null) {
instance = new LazySingleton(); //創(chuàng)建單例實(shí)例
}
}
}
return instance;
}
}
餓漢式單例類VS懶漢式單例類
餓漢式單例類在類被加載時(shí)就將自己實(shí)例化投蝉,它的優(yōu)點(diǎn)在于無須考慮多線程訪問問題,可以確保實(shí)例的唯一性征堪;從調(diào)用速度和反應(yīng)時(shí)間角度來講瘩缆,由于單例對(duì)象一開始就得以創(chuàng)建,因此要優(yōu)于懶漢式單例佃蚜。但是無論系統(tǒng)在運(yùn)行時(shí)是否需要使用該單例對(duì)象庸娱,由于在類加載時(shí)該對(duì)象就需要?jiǎng)?chuàng)建着绊,因此從資源利用效率角度來講,餓漢式單例不及懶漢式單例熟尉,而且在系統(tǒng)加載時(shí)由于需要?jiǎng)?chuàng)建餓漢式單例對(duì)象归露,加載時(shí)間可能會(huì)比較長(zhǎng)。
懶漢式單例類在第一次使用時(shí)創(chuàng)建斤儿,無須一直占用系統(tǒng)資源剧包,實(shí)現(xiàn)了延遲加載,但是必須處理好多個(gè)線程同時(shí)訪問的問題往果,特別是當(dāng)單例類作為資源控制器疆液,在實(shí)例化時(shí)必然涉及資源初始化,而資源初始化很有可能耗費(fèi)大量時(shí)間棚放,這意味著出現(xiàn)多線程同時(shí)首次引用此類的機(jī)率變得較大枚粘,需要通過雙重檢查鎖定等機(jī)制進(jìn)行控制,這將導(dǎo)致系統(tǒng)性能受到一定影響飘蚯。
IoDH(Initialization Demand Holder )
餓漢式單例類不能實(shí)現(xiàn)延遲加載馍迄,不管將來用不用始終占據(jù)內(nèi)存;懶漢式單例類線程安全控制煩瑣局骤,而且性能受影響攀圈。可見峦甩,無論是餓漢式單例還是懶漢式單例都存在這樣那樣的問題赘来,有沒有一種方法,能夠?qū)煞N單例的缺點(diǎn)都克服凯傲,而將兩者的優(yōu)點(diǎn)合二為一呢犬辰?答案是:Yes!下面我們來學(xué)習(xí)這種更好的被稱之為Initialization Demand Holder (IoDH)的技術(shù)冰单。
在IoDH中幌缝,我們?cè)趩卫愔性黾右粋€(gè)靜態(tài)(static)內(nèi)部類,在該內(nèi)部類中創(chuàng)建單例對(duì)象诫欠,再將該單例對(duì)象通過getInstance()方法返回給外部使用涵卵,實(shí)現(xiàn)代碼如下所示:
//Initialization on Demand Holder
class Singleton {
private Singleton() {
}
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
public static void main(String args[]) {
Singleton s1, s2;
s1 = Singleton.getInstance();
s2 = Singleton.getInstance();
System.out.println(s1==s2);
}
}
編譯并運(yùn)行上述代碼,運(yùn)行結(jié)果為:true荒叼,即創(chuàng)建的單例對(duì)象s1和s2為同一對(duì)象轿偎。由于靜態(tài)單例對(duì)象沒有作為Singleton的成員變量直接實(shí)例化,因此類加載時(shí)不會(huì)實(shí)例化Singleton被廓,第一次調(diào)用getInstance()時(shí)將加載內(nèi)部類HolderClass坏晦,在該內(nèi)部類中定義了一個(gè)static類型的變量instance,此時(shí)會(huì)首先初始化這個(gè)成員變量,由Java虛擬機(jī)來保證其線程安全性昆婿,確保該成員變量只能初始化一次间护。由于getInstance()方法沒有任何線程鎖定,因此其性能不會(huì)造成任何影響挖诸。
通過使用IoDH,我們既可以實(shí)現(xiàn)延遲加載法精,又可以保證線程安全多律,不影響系統(tǒng)性能,不失為一種最好的Java語(yǔ)言單例模式實(shí)現(xiàn)方式(其缺點(diǎn)是與編程語(yǔ)言本身的特性相關(guān)搂蜓,很多面向?qū)ο笳Z(yǔ)言不支持IoDH)狼荞。
總結(jié)
優(yōu)點(diǎn)
- 單例模式提供了對(duì)唯一實(shí)例的受控訪問。因?yàn)閱卫惙庋b了它的唯一實(shí)例帮碰,所以它可以嚴(yán)格控制客戶怎樣以及何時(shí)訪問它
- 由于在系統(tǒng)內(nèi)存中只存在一個(gè)對(duì)象相味,因此可以節(jié)約系統(tǒng)資源,對(duì)于一些需要頻繁創(chuàng)建和銷毀的對(duì)象單例模式無疑可以提高系統(tǒng)的性能
- 允許可變數(shù)目的實(shí)例殉挽》嵘妫基于單例模式我們可以進(jìn)行擴(kuò)展,使用與單例控制相似的方法來獲得指定個(gè)數(shù)的對(duì)象實(shí)例斯碌,既節(jié)省系統(tǒng)資源一死,又解決了單例單例對(duì)象共享過多有損性能的問題
缺點(diǎn)
- 由于單例模式中沒有抽象層,因此單例類的擴(kuò)展有很大的困難
- 單例類的職責(zé)過重傻唾,在一定程度上違背了“單一職責(zé)原則”投慈。因?yàn)閱卫惣瘸洚?dāng)了工廠角色,提供了工廠方法冠骄,同時(shí)又充當(dāng)了產(chǎn)品角色伪煤,包含一些業(yè)務(wù)方法,將產(chǎn)品的創(chuàng)建和產(chǎn)品的本身的功能融合到一起
- 現(xiàn)在很多面向?qū)ο笳Z(yǔ)言(如Java凛辣、C#)的運(yùn)行環(huán)境都提供了自動(dòng)垃圾回收的技術(shù)抱既,因此,如果實(shí)例化的共享對(duì)象長(zhǎng)時(shí)間不被利用蟀给,系統(tǒng)會(huì)認(rèn)為它是垃圾蝙砌,會(huì)自動(dòng)銷毀并回收資源,下次利用時(shí)又將重新實(shí)例化跋理,這將導(dǎo)致共享的單例對(duì)象狀態(tài)的丟失
場(chǎng)景
- 系統(tǒng)只需要一個(gè)實(shí)例對(duì)象择克,如系統(tǒng)要求提供一個(gè)唯一的序列號(hào)生成器或資源管理器,或者需要考慮資源消耗太大而只允許創(chuàng)建一個(gè)對(duì)象
- 客戶調(diào)用類的單個(gè)實(shí)例只允許使用一個(gè)公共訪問點(diǎn)前普,除了該公共訪問點(diǎn)肚邢,不能通過其他途徑訪問該實(shí)例