應(yīng)用最廣泛的設(shè)計(jì)模式-單例模式(1)

寫在前面#

其實(shí)我也不知道想說什么括袒,來簡書已經(jīng)足足15天了次兆,每天都在通過文字總結(jié)和分享自己的所學(xué)知識,寫技術(shù)文章好像也已經(jīng)成了生活中的一部分了锹锰,但是芥炭,更重要的是,我要告訴你們恃慧,作為一只異地戀狗在大街上看見別的情侶秀恩愛园蝠,真的會(huì)超想她,有沒有和我一樣的同胞(掩面失聲痛哭)痢士,如果我的心情一片黑暗彪薛,我將用學(xué)習(xí)將它照亮!


單例模式####

單例模式(Singleton pattern)是一種常用的軟件設(shè)計(jì)模式怠蹂。在它的核心結(jié)構(gòu)中只包含一個(gè)被稱為單例的特殊類善延。通過單例模式可以保證系統(tǒng)中一個(gè)類只有一個(gè)實(shí)例,要實(shí)現(xiàn)這一點(diǎn)城侧,可以從客戶端對其進(jìn)行實(shí)例化開始易遣。因此需要用一種只允許生成對象類的唯一實(shí)例的機(jī)制,“阻止”所有想要生成對象的訪問嫌佑。使用工廠方法來限制實(shí)例化過程豆茫。這個(gè)方法應(yīng)該是靜態(tài)方法(類方法),因?yàn)樽岊惖膶?shí)例去生成另一個(gè)唯一實(shí)例毫無意義歧强。其定義是: 一個(gè)類有且僅有一個(gè)實(shí)例澜薄,并且自行實(shí)例化向整個(gè)系統(tǒng)提供为肮。
在安卓中摊册,我們很多時(shí)候只需要擁有一個(gè)全局對象,這樣有利于我們協(xié)調(diào)整體的行為操作颊艳,比如ImageLoader實(shí)例茅特,在一個(gè)應(yīng)用中只應(yīng)該存在一個(gè)忘分,因?yàn)樵贗mageLoader中又包含了線程池、緩存系統(tǒng)白修、網(wǎng)絡(luò)請求等等妒峦,他們是非常消耗資源的,因此兵睛,沒有理由讓它無限制地實(shí)例化肯骇,這種我們覺得不能隨意地構(gòu)造對象的情況,就是單例模式的應(yīng)用場景了祖很。
實(shí)現(xiàn)單例模式笛丙,有幾個(gè)關(guān)鍵點(diǎn):

  • 構(gòu)造函數(shù)不對外開放,一般為private假颇。
  • 確保單例類的對象有且只有一個(gè)胚鸯,尤其是在多線程的環(huán)境下。
  • 通過一個(gè)靜態(tài)方法或者枚舉返回單例對象笨鸡。
  • 確保單例對象在反序列化的時(shí)候不會(huì)重新構(gòu)造對象姜钳。

下面我們就來進(jìn)行一個(gè)簡單的實(shí)例演示。正如我們知道的形耗,在一家公司中通常只有一個(gè)CEO哥桥,CEO對于公司而言是唯一的,此處我們使用CEO為模擬示例激涤。

public class CEO {
    private static final CEO mCEO = new CEO();

    private CEO() {}

    public static CEO getCEO() {
        return mCEO;
    }
}

非常簡單的代碼泰讽,CEO類不能通過new的形式構(gòu)造對象,還能通過CEO.getCEO()函數(shù)來獲取昔期,而這個(gè)CEO對象又是靜態(tài)(static)對象并且在聲明的時(shí)候就已經(jīng)初始化了已卸,這就保證了CEO對象的唯一性。此處介紹的是單例模式的一種硼一,叫做餓漢單例模式累澡。
我們繼續(xù)學(xué)習(xí)一下其他的各種單例模式,作者會(huì)盡量解釋幾種實(shí)現(xiàn)單例模式的區(qū)別般贼。

  • 懶漢模式#####

懶漢模式是聲明一個(gè)靜態(tài)對象愧哟,并且在用戶第一次調(diào)用getInstance時(shí)進(jìn)行初始化,而上面所講述的CEO類則是在聲明靜態(tài)對象的時(shí)候就已經(jīng)完成了實(shí)例化操作哼蛆。懶漢單例模式的實(shí)現(xiàn)方式如下:.

public class CEO {
    private static CEO instance;

    private CEO() {}

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

善于發(fā)現(xiàn)的朋友又看到了蕊梧,在此處的getInstance方法中有一個(gè)關(guān)鍵字synchronized,對synchronized關(guān)鍵字不理解的同學(xué)腮介,請?zhí)D(zhuǎn)到深入理解java中的synchronized關(guān)鍵字肥矢。
關(guān)鍵字synchronized在此處聲明getInstance為一個(gè)同步方法,也就是上面所說的在多線程情況下保證單例對象唯一性的手段叠洗。
但是此處存在一個(gè)問題甘改,即使我們的Instance已經(jīng)被初始化了(第一次調(diào)用時(shí)就會(huì)初始化)旅东,每次調(diào)用getInstance方法都會(huì)進(jìn)行同步,這樣會(huì)小號不必要的資源十艾,這也是懶漢模式所存在的最大問題抵代。那么,通過上述內(nèi)容忘嫉,我們對懶漢模式做一個(gè)小小的總結(jié)荤牍。

  • 優(yōu)點(diǎn): 懶漢單例模式只有在使用時(shí)才會(huì)將類實(shí)例化,在一定程度上節(jié)約了資源庆冕。
  • 缺點(diǎn): 第一次加載時(shí)需要進(jìn)行實(shí)例化参淫,反應(yīng)稍慢,最大的問題是每次調(diào)用getInstance方法都需要進(jìn)行同步愧杯,造成不必要的同步開銷涎才。

這種方式一般不推薦使用。

  • Double Check Lock (DLC)實(shí)現(xiàn)單例#####

這里我們直接上示例代碼

public class CEO {
    private static CEO sInstance = null;

    private CEO() {
    }

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

我們的關(guān)注點(diǎn)依然在getInstance方法上力九,可以看到我們在該方法中對sInstance做了兩次判斷是否為空:

  • 第一層判斷主要為了避免不必要的同步耍铜。
  • 第二層為了在null的情況下創(chuàng)建實(shí)例。

估計(jì)此時(shí)會(huì)有部分朋友不知道我在說什么了跌前,沒關(guān)系棕兼,我們一起來進(jìn)行分析:假設(shè)線程A執(zhí)行到sInstance = new CEO();這里看到的只是簡單的一行代碼,但實(shí)際上會(huì)進(jìn)行多條匯編指令抵乓,主要做了三件事情:

  1. 給CEO的實(shí)例分配內(nèi)存伴挚。
  2. 調(diào)用CEO的構(gòu)造函數(shù),初始化成員字段灾炭。
  3. 將sInstance對象只想分配的內(nèi)存空間中(完成此步驟時(shí)茎芋,我們的sInstance就不為null了)

以下內(nèi)容可根據(jù)理解能力選擇是否觀看

Java編譯器是允許程序亂序執(zhí)行的,以及JDK1.5之前的JMM(Java Memory Model蜈出,Java內(nèi)存模型)中Cache田弥、寄存器到主內(nèi)存回寫順序規(guī)定,上面第二和第三的順序是無法保證的铡原,也就是說偷厦,執(zhí)行順序可能是1-3-2,也可能是1-2-3燕刻。如果是前者只泼,并且在3執(zhí)行完畢、2未執(zhí)行之前卵洗,被切換到線程B上请唱,這時(shí)候sInstance因?yàn)橐呀?jīng)在線程A內(nèi)執(zhí)行過第三點(diǎn),sInstance已經(jīng)是非空的了,所以籍滴,線程B會(huì)直接取走sInstance酪夷,然而在實(shí)際上它并沒有走完過程2榴啸,再使用時(shí)就會(huì)報(bào)錯(cuò)孽惰,這就是DCL失效問題。
在JDK1.5以后鸥印,SUN官方調(diào)整了JVM勋功,具體化了volatile關(guān)鍵字,因此库说,如果是JDK1.5或者之后的版本狂鞋,只需要將sInstance的聲明改為private volatile static CEO sInstance = null就可以保證sInstance對象每次都是從主內(nèi)存中讀取的,此時(shí)潜的,使用DCL來完成單例模式一般是不會(huì)出錯(cuò)的骚揍,當(dāng)然,volatile關(guān)鍵字也會(huì)犧牲一定的程序性能啰挪,但為了程序穩(wěn)定信不,犧牲一丁點(diǎn)性能,是值得的亡呵。

根據(jù)以上所述抽活,DLC模式我們同樣地進(jìn)行適當(dāng)總結(jié):

  • 優(yōu)點(diǎn)
    1. 資源利用率高,第一次執(zhí)行getInstance時(shí)單例對象才會(huì)被實(shí)例化锰什,效率高
  • 缺點(diǎn)
    1. 第一次加載時(shí)反應(yīng)較慢下硕,也由于Java內(nèi)存模型原因有時(shí)候會(huì)失敗。
    2. 在高并發(fā)場景下有一定缺陷汁胆。
    3. DLC模式能夠在需要的時(shí)候才實(shí)例化單例對象梭姓,并且能夠哎絕大多數(shù)場景下保證單例對象的唯一性,除非你的代碼在高并發(fā)場景比較復(fù)雜或者低于JDK1.6的版本下使用嫩码,否則糊昙,這種方式一般能夠滿足需求。
  • 靜態(tài)內(nèi)部單例模式#####

DLC模式雖然在一定程度上解決了資源消耗谢谦、多余的同步释牺、線程安全燈問題,但是回挽,它還是在某些情況下出現(xiàn)失效問題没咙。這個(gè)問題被成為雙重檢查鎖定(DLC)失效,在《Java并發(fā)編程與實(shí)踐》一書中千劈,指出這種“優(yōu)化”是丑陋的祭刚,不贊成使用,而建議使用如下代碼替代:

public class CEO {

    private CEO() {}

    public static synchronized CEO getInstance() {
        return CEO_Holder.sInstance;
    }

    private static class CEO_Holder {
        private static final CEO sInstance = new CEO();
    }
}

當(dāng)?shù)谝患虞dCEO類的時(shí)候并不會(huì)初始化sInstance,只有在第一次調(diào)用getInstance方法時(shí)它才會(huì)被初始化涡驮,因此暗甥,第一次調(diào)用getInstance方法會(huì)導(dǎo)致虛擬機(jī)加載CEO_Holder類,這種方式不僅能夠保證線程安全捉捅,還能夠保證單例對象的唯一性撤防,同時(shí)也延遲了單例的實(shí)例化,所以這是推薦使用的單例模式實(shí)現(xiàn)方式棒口。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寄月,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子无牵,更是在濱河造成了極大的恐慌漾肮,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茎毁,死亡現(xiàn)場離奇詭異克懊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)七蜘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門谭溉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崔梗,你說我怎么就攤上這事夜只。” “怎么了蒜魄?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵扔亥,是天一觀的道長。 經(jīng)常有香客問我谈为,道長旅挤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任伞鲫,我火速辦了婚禮粘茄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秕脓。我一直安慰自己柒瓣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布吠架。 她就那樣靜靜地躺著芙贫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪傍药。 梳的紋絲不亂的頭發(fā)上磺平,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天魂仍,我揣著相機(jī)與錄音,去河邊找鬼拣挪。 笑死擦酌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的菠劝。 我是一名探鬼主播赊舶,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼闸英!你這毒婦竟也來了锯岖?” 一聲冷哼從身側(cè)響起介袜,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤甫何,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后遇伞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辙喂,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年鸠珠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巍耗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渐排,死狀恐怖炬太,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驯耻,我是刑警寧澤亲族,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站可缚,受9級特大地震影響霎迫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帘靡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一知给、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧描姚,春花似錦涩赢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赃阀,卻和暖如春霎肯,著一層夾襖步出監(jiān)牢的瞬間擎颖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工观游, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搂捧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓懂缕,卻偏偏與公主長得像允跑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子搪柑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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