【設(shè)計(jì)模式筆記】(一)- 單例模式

1.簡(jiǎn)述

單例模式是應(yīng)用最廣泛的模式之一,定義就是單例對(duì)象的類(lèi)必須保證只有一個(gè)實(shí)例存在太雨。單例模式適用于創(chuàng)建一個(gè)對(duì)象需要消耗過(guò)多資源的情況吟榴,例如訪問(wèn)數(shù)據(jù)庫(kù)等資源是需要考慮使用。

實(shí)現(xiàn)單例模式的關(guān)鍵點(diǎn)如下:

  • 構(gòu)造函數(shù)私有化(才不會(huì)讓你有機(jī)會(huì)再創(chuàng)建一個(gè)對(duì)象)
  • 通過(guò)一個(gè)靜態(tài)方法或枚舉(后面會(huì)有舉例)返回單例類(lèi)對(duì)象
  • 確保單例類(lèi)的對(duì)象有且只有一個(gè)囊扳,尤其是多線程環(huán)境下(同時(shí)是難點(diǎn))
  • 確保單例類(lèi)的對(duì)象在反序列化是不會(huì)重新構(gòu)建對(duì)象

2.實(shí)現(xiàn)

餓漢式

public class Singleton {
    private final static Singleton instance = new Singleton();
    //私有化構(gòu)造器
    private Singleton(){}
    
    //共有靜態(tài)方法吩翻,對(duì)外暴露獲取單例對(duì)象
    public static Singleton getInstance(){
        return instance;
    }
}

可以看到餓漢式是在聲明靜態(tài)對(duì)象時(shí)就已經(jīng)初始化了兜看,如果沒(méi)有使用單例對(duì)象的情況下,就會(huì)造成不必要的內(nèi)存開(kāi)銷(xiāo)狭瞎。

懶漢式

懶漢式是聲明一個(gè)靜態(tài)對(duì)象细移,在第一次調(diào)用getInstance()時(shí)進(jìn)行初始化

public class Singleton {
    private static Singleton instance = null;

    private Singleton(){}

    public static synchronized Singleton getInstance(){
        if(null == instance){
            instance = new Singleton();
        }
        return instance;
    }
}

與餓漢式不同的地方不僅僅是單例對(duì)象初始化的時(shí)機(jī),會(huì)發(fā)現(xiàn)getInstance()方法前添加了synchronized關(guān)鍵字熊锭,也就是getInstance()是一個(gè)同步方法弧轧,以此來(lái)保證多線程情況下單例對(duì)象的唯一。

相對(duì)的碗殷,每次調(diào)用getInstance()都會(huì)進(jìn)行同步精绎,就會(huì)消耗不必要的資源,也是懶漢式存在的最大問(wèn)題亿扁。

Double Check Lock(DCL)

DCL方式實(shí)現(xiàn)單力模式的優(yōu)點(diǎn)在于既能在需要時(shí)才初始化對(duì)象捺典,又能保證線程安全鸟廓,而且在對(duì)象初始化之后調(diào)用getInstance()不進(jìn)行同步

public class Singleton {
    private volatile static Singleton instance = null;

    private Singleton(){}

    public static Singleton getInstance(){
        if(null == instance){
            synchronized(Singleton.class){
                if(null == instance){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

可以看到getInstance()方法中對(duì)instance進(jìn)行了兩次判空从祝,第一次判斷是為了判斷不必要的同步,第二次判斷是為了在null的情況下穿件實(shí)例引谜;同時(shí)instance對(duì)象前面還添加了volatile關(guān)鍵字牍陌,如果不使用volatile關(guān)鍵字的話無(wú)法保證instance的原子性,這里涉及到instance = new Singleton();語(yǔ)句不是一個(gè)原子操作员咽。

這句代碼最終會(huì)被編譯成多條匯編指令毒涧,大致做了3件事:

  1. 給Singleton的實(shí)力分配內(nèi)存
  2. 調(diào)用Singleton()的構(gòu)造函數(shù),初始化成員字段
  3. instance對(duì)象指向分配的內(nèi)存空間(此時(shí)instance就不是null了)

由于Java編譯器允許處理器亂序執(zhí)行贝室,以及JDK1.5之前JMM(Java Memory Model契讲,即java內(nèi)存模型)中得Cache、寄存器到主內(nèi)存回寫(xiě)順序的規(guī)定滑频,第二和第三的順序是無(wú)法保證捡偏。也就是說(shuō)執(zhí)行順序可能是1-2-3或者是1-3-2。如果是后者峡迷,并在3執(zhí)行完成银伟、2為執(zhí)行前,備切換到線程B绘搞,這時(shí)候instance已經(jīng)在線程A中執(zhí)行過(guò)了3彤避,instance已經(jīng)是非空了,所以線程B直接取走instance使用時(shí)會(huì)出錯(cuò)夯辖,導(dǎo)致DCL模式失效琉预,而且這種情況難以重現(xiàn)的錯(cuò)誤很可能會(huì)隱藏很久。

在JDK1.5之后蒿褂,調(diào)整了JVM圆米,具體化了volatile關(guān)鍵字尖阔。所以在JDK1,5之后的版本在instance前添加volatile關(guān)鍵字保證每次都是從主內(nèi)存中讀取就可以使用DCL模式來(lái)完成代理模式了榨咐。當(dāng)然介却,volatile或多或少會(huì)影響到性能,考慮到正確性這點(diǎn)性能的犧牲還是值得的块茁。

DCL模式能夠在絕大多數(shù)場(chǎng)景下保證單例對(duì)象的唯一性齿坷,資源利用率高,只有第一次加載時(shí)反應(yīng)稍慢数焊,一般能夠滿足需求

靜態(tài)內(nèi)部類(lèi)

在某些情況下DCL模式會(huì)出現(xiàn)時(shí)效的問(wèn)題永淌,于是邊有了靜態(tài)內(nèi)部類(lèi)的實(shí)現(xiàn)方式

public class Singleton {
    private Singleton(){}
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
    
    /**靜態(tài)內(nèi)部類(lèi)*/
    private static class SingletonHolder{
        public static final Singleton instance = new Singleton();
    }
}

當(dāng)?shù)谝淮渭虞dSingleton類(lèi)時(shí)并不會(huì)初始化instance,只有在第一次調(diào)用getInstance()方法是回初始化佩耳。第一次調(diào)用getInstance()方法導(dǎo)致虛擬機(jī)加載SingletonHolder類(lèi)遂蛀,這種方式嫩確保線程安全,也能確保單例對(duì)象的唯一性干厚,同時(shí)也延遲了單例對(duì)象的實(shí)例化李滴,所以推薦使用這種實(shí)現(xiàn)方式。

枚舉單例

public enmu SingletonEnum {
    INSTANCE;
}

就是這么簡(jiǎn)單粗暴蛮瞄,其實(shí)最大優(yōu)點(diǎn)在于關(guān)鍵點(diǎn)的第4點(diǎn)所坯,即是反序列化也不會(huì)重新生成新的實(shí)例。

通過(guò)序列化可以將單例對(duì)象寫(xiě)到磁盤(pán)挂捅,然后在讀取出來(lái)芹助,即使構(gòu)造函數(shù)是私有的,反序列化時(shí)依然可以通過(guò)特殊的方式創(chuàng)建一個(gè)新的實(shí)例闲先。反序列化操作提供了一個(gè)很特別的鉤子函數(shù)状土,類(lèi)中具有一個(gè)私有的、被實(shí)例化的方法readResolve()伺糠,這個(gè)方法可以讓開(kāi)發(fā)人員控制對(duì)象的反序列化蒙谓。上述幾個(gè)實(shí)例中如果要避免反序列化是重新生成對(duì)象,必須加入如下方法:

private Object readResolve() throws ObjectStreamException{
    return instance;
}

使用容器實(shí)現(xiàn)單例模式

public class SingletonManager{
    private static Map<String,Object> objMap = new HashMap<>();
    
    private SingletonManager(){}
    
    public static void registerService(String key,Object instance){
        if(!objMap.containsKey(key)){
            objMap.put(key,instance);
        }
    }
    
    public static Object getService(String key){
        return objMap.get(key);
    }
}

這種方式使得我們可以管理多種類(lèi)型的單例退盯,并且在使用時(shí)可以統(tǒng)一的接口進(jìn)行操作彼乌,降低了使用成本,同時(shí)隱藏了具體實(shí)現(xiàn)降低了耦合渊迁。

其實(shí)Android中LayoutInflater就是以這種方式實(shí)現(xiàn)的慰照。

3.總結(jié)

不管哪種形式實(shí)現(xiàn)單例模式,核心原理都是那四個(gè)關(guān)鍵點(diǎn)琉朽,具體選擇哪種實(shí)現(xiàn)方式取決于項(xiàng)目本身以及具體的開(kāi)發(fā)環(huán)境等等毒租。

而對(duì)于客戶(hù)端來(lái)說(shuō)通常沒(méi)有高并發(fā)的情況,推薦使用DCL模式或者是靜態(tài)內(nèi)部類(lèi)的方式實(shí)現(xiàn)箱叁。

優(yōu)點(diǎn)

  • 只存在一個(gè)實(shí)例墅垮,減少了內(nèi)存開(kāi)支惕医,減少了系統(tǒng)的性能開(kāi)銷(xiāo)
  • 避免對(duì)資源的多重占用
  • 全局的訪問(wèn)點(diǎn),優(yōu)化和共享資源訪問(wèn)

缺點(diǎn)

  • 沒(méi)有接口算色,難擴(kuò)展抬伺,只能修改代碼
  • 如果持有Context容易導(dǎo)致內(nèi)存泄露(需要傳遞Context的話最好是Application Context)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市灾梦,隨后出現(xiàn)的幾起案子峡钓,更是在濱河造成了極大的恐慌,老刑警劉巖若河,帶你破解...
    沈念sama閱讀 223,207評(píng)論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件能岩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡萧福,警方通過(guò)查閱死者的電腦和手機(jī)拉鹃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鲫忍,“玉大人膏燕,你說(shuō)我怎么就攤上這事∷橇” “怎么了煌寇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 170,031評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵焕蹄,是天一觀的道長(zhǎng)逾雄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)腻脏,這世上最難降的妖魔是什么鸦泳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,334評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮永品,結(jié)果婚禮上做鹰,老公的妹妹穿的比我還像新娘。我一直安慰自己鼎姐,他們只是感情好钾麸,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著炕桨,像睡著了一般饭尝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上献宫,一...
    開(kāi)封第一講書(shū)人閱讀 52,895評(píng)論 1 314
  • 那天钥平,我揣著相機(jī)與錄音,去河邊找鬼姊途。 笑死涉瘾,一個(gè)胖子當(dāng)著我的面吹牛知态,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播立叛,決...
    沈念sama閱讀 41,300評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼负敏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了秘蛇?” 一聲冷哼從身側(cè)響起原在,我...
    開(kāi)封第一講書(shū)人閱讀 40,264評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎彤叉,沒(méi)想到半個(gè)月后庶柿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,784評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秽浇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評(píng)論 3 343
  • 正文 我和宋清朗相戀三年浮庐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柬焕。...
    茶點(diǎn)故事閱讀 40,989評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡审残,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斑举,到底是詐尸還是另有隱情搅轿,我是刑警寧澤,帶...
    沈念sama閱讀 36,649評(píng)論 5 351
  • 正文 年R本政府宣布富玷,位于F島的核電站璧坟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赎懦。R本人自食惡果不足惜雀鹃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望励两。 院中可真熱鬧黎茎,春花似錦、人聲如沸当悔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,814評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盲憎。三九已至嗅骄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間焙畔,已是汗流浹背掸读。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,940評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人儿惫。 一個(gè)月前我還...
    沈念sama閱讀 49,452評(píng)論 3 379
  • 正文 我出身青樓澡罚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親肾请。 傳聞我的和親對(duì)象是個(gè)殘疾皇子留搔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評(píng)論 2 361

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

  • 前言 本文主要參考 那些年,我們一起寫(xiě)過(guò)的“單例模式”铛铁。 何為單例模式隔显? 顧名思義,單例模式就是保證一個(gè)類(lèi)僅有一個(gè)...
    tandeneck閱讀 2,517評(píng)論 1 8
  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡(jiǎn)單饵逐、最易理解的設(shè)計(jì)模式括眠,也因?yàn)樗暮?jiǎn)潔易懂,是項(xiàng)目中最...
    成熱了閱讀 4,259評(píng)論 4 34
  • 一.什么是單例模式 單例模式的定義:確保一個(gè)類(lèi)只有一個(gè)實(shí)例倍权,并提供一個(gè)訪問(wèn)他的全局訪問(wèn)點(diǎn)掷豺。單例模式是幾個(gè)設(shè)計(jì)模式中...
    Geeks_Liu閱讀 2,230評(píng)論 0 10
  • 1 單例模式的動(dòng)機(jī) 對(duì)于一個(gè)軟件系統(tǒng)的某些類(lèi)而言,我們無(wú)須創(chuàng)建多個(gè)實(shí)例薄声。舉個(gè)大家都熟知的例子——Windows任務(wù)...
    justCode_閱讀 1,433評(píng)論 2 9
  • :愛(ài)一個(gè)女人一生 意味著你要去愛(ài)一個(gè)少女 一個(gè)少婦 一個(gè)忙忙碌碌的中年婦女 以及一個(gè)嘮嘮叨叨的老太太 紅玫瑰與白玫...
    達(dá)摩流浪者zkl閱讀 171評(píng)論 0 0