單例模式

概述

單例模式(Singleton Pattern):確保某一個(gè)類只有一個(gè)實(shí)例誓篱,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,這個(gè)類稱為單例類托猩,它提供全局訪問的方法肉微。單例模式是一種對(duì)象創(chuàng)建型模式。

單例模式有三個(gè)要點(diǎn):

  1. 某個(gè)類只能有一個(gè)實(shí)例
  2. 它必須自行創(chuàng)建這個(gè)實(shí)例
  3. 是它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例酬核。

單例模式是結(jié)構(gòu)最簡(jiǎn)單的設(shè)計(jì)模式一蜜另,在它的核心結(jié)構(gòu)中只包含一個(gè)被稱為單例類的特殊類。單例模式結(jié)構(gòu)如圖所示:

單例模式結(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)圖如圖所示

餓漢式單例類結(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)圖如圖所示:

懶漢式單例類結(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)

  1. 單例模式提供了對(duì)唯一實(shí)例的受控訪問。因?yàn)閱卫惙庋b了它的唯一實(shí)例帮碰,所以它可以嚴(yán)格控制客戶怎樣以及何時(shí)訪問它
  2. 由于在系統(tǒng)內(nèi)存中只存在一個(gè)對(duì)象相味,因此可以節(jié)約系統(tǒng)資源,對(duì)于一些需要頻繁創(chuàng)建和銷毀的對(duì)象單例模式無疑可以提高系統(tǒng)的性能
  3. 允許可變數(shù)目的實(shí)例殉挽》嵘妫基于單例模式我們可以進(jìn)行擴(kuò)展,使用與單例控制相似的方法來獲得指定個(gè)數(shù)的對(duì)象實(shí)例斯碌,既節(jié)省系統(tǒng)資源一死,又解決了單例單例對(duì)象共享過多有損性能的問題

缺點(diǎn)

  1. 由于單例模式中沒有抽象層,因此單例類的擴(kuò)展有很大的困難
  2. 單例類的職責(zé)過重傻唾,在一定程度上違背了“單一職責(zé)原則”投慈。因?yàn)閱卫惣瘸洚?dāng)了工廠角色,提供了工廠方法冠骄,同時(shí)又充當(dāng)了產(chǎn)品角色伪煤,包含一些業(yè)務(wù)方法,將產(chǎn)品的創(chuàng)建和產(chǎn)品的本身的功能融合到一起
  3. 現(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)景

  1. 系統(tǒng)只需要一個(gè)實(shí)例對(duì)象择克,如系統(tǒng)要求提供一個(gè)唯一的序列號(hào)生成器或資源管理器,或者需要考慮資源消耗太大而只允許創(chuàng)建一個(gè)對(duì)象
  2. 客戶調(diào)用類的單個(gè)實(shí)例只允許使用一個(gè)公共訪問點(diǎn)前普,除了該公共訪問點(diǎn)肚邢,不能通過其他途徑訪問該實(shí)例

轉(zhuǎn)自:確保對(duì)象的唯一性——單例模式 (三)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子骡湖,更是在濱河造成了極大的恐慌贱纠,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件响蕴,死亡現(xiàn)場(chǎng)離奇詭異谆焊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)浦夷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門辖试,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人劈狐,你說我怎么就攤上這事罐孝。” “怎么了肥缔?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵莲兢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我续膳,道長(zhǎng)改艇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任坟岔,我火速辦了婚禮源梭,結(jié)果婚禮上繁成,老公的妹妹穿的比我還像新娘焰雕。我一直安慰自己记焊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布瘦穆。 她就那樣靜靜地躺著纪隙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扛或。 梳的紋絲不亂的頭發(fā)上绵咱,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音熙兔,去河邊找鬼悲伶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛住涉,可吹牛的內(nèi)容都是我干的麸锉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼舆声,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼花沉!你這毒婦竟也來了柳爽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤碱屁,失蹤者是張志新(化名)和其女友劉穎磷脯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娩脾,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赵誓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柿赊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片架曹。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闹瞧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情展辞,我是刑警寧澤奥邮,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站罗珍,受9級(jí)特大地震影響洽腺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜覆旱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一蘸朋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扣唱,春花似錦藕坯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至正歼,卻和暖如春辐马,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背局义。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工喜爷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人萄唇。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓檩帐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親另萤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子轿塔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡(jiǎn)單、最易理解的設(shè)計(jì)模式,也因?yàn)樗暮?jiǎn)潔易懂勾缭,是項(xiàng)目中最...
    成熱了閱讀 4,231評(píng)論 4 34
  • 【學(xué)習(xí)難度:★☆☆☆☆揍障,使用頻率:★★★★☆】直接出處:?jiǎn)卫J绞崂砗蛯W(xué)習(xí):https://github.com/...
    BruceOuyang閱讀 667評(píng)論 1 2
  • 1 單例模式的動(dòng)機(jī) 對(duì)于一個(gè)軟件系統(tǒng)的某些類而言,我們無須創(chuàng)建多個(gè)實(shí)例俩由。舉個(gè)大家都熟知的例子——Windows任務(wù)...
    justCode_閱讀 1,432評(píng)論 2 9
  • 1 場(chǎng)景問題# 1.1 讀取配置文件的內(nèi)容## 考慮這樣一個(gè)應(yīng)用毒嫡,讀取配置文件的內(nèi)容。 很多應(yīng)用項(xiàng)目幻梯,都有與應(yīng)用相...
    七寸知架構(gòu)閱讀 6,679評(píng)論 12 68
  • 我們小心翼翼的呵護(hù)著自己的所有兜畸,卻在迷迷糊糊中死去! 結(jié)果是?沒有什麼是我的碘梢,也沒有什麼是你的咬摇。 《潮騷~蔡振源》
    蔡振源閱讀 125評(píng)論 0 4