設(shè)計(jì)模式之——單例模式

1 單例模式的定義

定義:確保某一個(gè)類只有一個(gè)實(shí)例,自行實(shí)例化并且向整個(gè)系統(tǒng)提供這個(gè)實(shí)例淮蜈。
單例模式的通用類圖如下所示:

單例模式的通用類圖

Singleton稱為單例類,通過(guò)使用private的構(gòu)造函數(shù)確保了在一個(gè)應(yīng)用中只產(chǎn)生一個(gè)實(shí)例(應(yīng)用啟動(dòng)的時(shí)候,自行實(shí)例化)浩村。

2 單例模式的優(yōu)點(diǎn)和缺點(diǎn)

單例模式的優(yōu)點(diǎn):

  1. 提高效率
    當(dāng)一個(gè)對(duì)象需要頻繁的創(chuàng)建和銷毀,并且對(duì)象的創(chuàng)建和銷毀操作性能無(wú)法優(yōu)化护蝶。此時(shí)华烟,單例模式可以在應(yīng)用啟動(dòng)的時(shí)候就產(chǎn)生一個(gè)實(shí)例,永久駐留內(nèi)存持灰,可以減少系統(tǒng)創(chuàng)建和銷毀實(shí)例的性能開(kāi)銷盔夜,非常明顯地提高效率。另外堤魁,單例模式在內(nèi)存中只有一個(gè)實(shí)例喂链,可以減少內(nèi)存開(kāi)支。比如妥泉,讀取配置等椭微。
  2. 避免對(duì)資源的多重占用
    例如一個(gè)對(duì)文件的寫(xiě)操作,由于只有一個(gè)實(shí)例盲链,避免對(duì)同一個(gè)資源文件同時(shí)寫(xiě)蝇率。
  3. 在系統(tǒng)設(shè)置全局訪問(wèn)點(diǎn),優(yōu)化和共享資源訪問(wèn)刽沾。

單例模式的缺點(diǎn):

  1. 單例模式一般沒(méi)有接口本慕,擴(kuò)展困難。
  2. 單例測(cè)試對(duì)于測(cè)試是不利的侧漓。在并行開(kāi)發(fā)環(huán)境中间狂,如果單例模式?jīng)]有完成,是不能進(jìn)行測(cè)試的火架。沒(méi)有接口鉴象,也不能使用mock的方式虛擬一個(gè)對(duì)象。
  3. 單例模式與單一職責(zé)原則有沖突何鸡。一個(gè)類應(yīng)該只實(shí)現(xiàn)一個(gè)邏輯纺弊,而不關(guān)心是否是單例的。單例模式把單例和業(yè)務(wù)邏輯融合在一個(gè)類中骡男。

3 單例模式的應(yīng)用場(chǎng)景

在一個(gè)系統(tǒng)中淆游,要求一個(gè)類有且僅有一個(gè)實(shí)例,如果出現(xiàn)多個(gè)實(shí)例就會(huì)出現(xiàn)副作用隔盛,可以采用單例模式犹菱。具體如下:

  1. 要求生成唯一序列號(hào)的環(huán)境。
  2. 在整個(gè)項(xiàng)目中需要共享一個(gè)訪問(wèn)點(diǎn)或者數(shù)據(jù)吮炕。
  3. 創(chuàng)建和銷毀一個(gè)對(duì)象需要消耗的資源過(guò)多腊脱,但是又經(jīng)常用到。如,要訪問(wèn)IO和數(shù)據(jù)庫(kù)連接等们豌。
  4. 需要定義大量的靜態(tài)常量和靜態(tài)方法(如工具類)的環(huán)境,可以采用單例模式(也可以直接聲明為static)开呐。
  5. 需要頻繁的進(jìn)行創(chuàng)建和銷毀的對(duì)象杜耙。

4 單例模式的最佳實(shí)踐

單例模式比較簡(jiǎn)單搜骡,也應(yīng)用廣泛。在Spring中佑女,每個(gè)Bean默認(rèn)是單例的记靡,優(yōu)點(diǎn)是Spring容器可以管理這些Bean的生命周期,決定對(duì)象的創(chuàng)建和銷毀時(shí)機(jī)团驱,以及創(chuàng)建和銷毀對(duì)象時(shí)的處理摸吠。

5 單例模式常見(jiàn)的實(shí)現(xiàn)方式

單例模式的實(shí)現(xiàn)可以分為兩類:餓漢式(饑漢式)和懶漢式。
餓漢式:在程序啟動(dòng)或單例模式類被加載的時(shí)候店茶,單例模式實(shí)例就已經(jīng)被創(chuàng)建蜕便。
懶漢式:當(dāng)程序第一次訪問(wèn)單例模式實(shí)例的時(shí)候才進(jìn)行創(chuàng)建劫恒。
以上兩種方式各有優(yōu)點(diǎn)贩幻。

  • 如果單例模式實(shí)例在系統(tǒng)中會(huì)被頻繁用到,餓漢式比較好
    優(yōu)點(diǎn):程序啟動(dòng)的時(shí)候已經(jīng)進(jìn)行了實(shí)例化两嘴,調(diào)用時(shí)直接返回實(shí)例丛楚,速度快,效率高憔辫。
    缺點(diǎn):如果實(shí)例使用頻率不高或者幾乎不用趣些,啟動(dòng)的時(shí)候就進(jìn)行實(shí)例化,浪費(fèi)內(nèi)存資源贰您。
  • 如果單例模式實(shí)例在系統(tǒng)中很少用到或者幾乎不會(huì)用到坏平,懶漢式較好
    優(yōu)點(diǎn):如果實(shí)例使用頻率不高或者幾乎不用,啟動(dòng)的時(shí)候就不進(jìn)行實(shí)例化锦亦,第一次調(diào)用的時(shí)候進(jìn)行實(shí)例化(lazy-loading)舶替,節(jié)約內(nèi)存資源。
    缺點(diǎn):?jiǎn)卫J降膶?shí)例如果被頻繁調(diào)用杠园,影響效率顾瞪。

5.1 餓漢式常見(jiàn)實(shí)現(xiàn)

5.1.1 推薦的實(shí)現(xiàn)(線程安全)

  1. 靜態(tài)變量初始化(最推薦)
public class Singleton(){
     private static Singleton instance = new Singleton();     
     // 私有構(gòu)造函數(shù)
     private Singleton(){
     }

     public static Singleton getInstance(){
         return instance ;
     } 
}
  1. 靜態(tài)代碼塊初始化(類似于1):
public class Singleton(){
     private static Singleton instance; 
     {
        instance = new Singleton();
     }
     // 私有構(gòu)造函數(shù)
     private Singleton(){
     }

     public static Singleton getInstance(){
         return instance ;
     } 
}
  1. 枚舉類
    單實(shí)例枚舉類SingletonEnum :
public enum Singleton {
    /**
     * 實(shí)例
     */
    INSTANCE;

    private Singleton() {

    }

    /**
     * 業(yè)務(wù)方法
     */
    public void doSomething() {
        //TODO 業(yè)務(wù)代碼
    }
}

使用:

public class SingletonDemo {
    public static void main(String[] args) {
        Singleton singleton = Singleton.INSTANCE;
        singleton.doSomething();
    }
}

默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,但是在枚舉中的其他任何方法由程序員自己負(fù)責(zé)抛蚁。如果你正在使用實(shí)例方法陈醒,那么你需要確保線程安全(如果它影響到其他對(duì)象的狀態(tài)的話)。
傳統(tǒng)單例存在的另外一個(gè)問(wèn)題是一旦你實(shí)現(xiàn)了序列化接口瞧甩,那么它們不再保持單例钉跷。但是枚舉單例,JVM對(duì)序列化有保證肚逸。
優(yōu)點(diǎn):有序列化和線程安全的保證尘应,代碼簡(jiǎn)單惶凝。

5.2 懶漢式常見(jiàn)實(shí)現(xiàn)

  1. 單次判斷實(shí)例為null
    適合單線程,不適合多線程【線程不安全】犬钢。
public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

這種寫(xiě)法起到了Lazy Loading的效果苍鲜,但是只能在單線程下使用。如果在多線程下玷犹,一個(gè)線程進(jìn)入了 if (singleton == null) 判斷語(yǔ)句塊混滔,還未來(lái)得及往下執(zhí)行,另一個(gè)線程也通過(guò)了這個(gè)判斷語(yǔ)句歹颓,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例坯屿。所以在多線程環(huán)境下不可使用這種方式。

  1. 同步方法獲取實(shí)例
    線程安全巍扛,但是效率低领跛,不推薦使用
public class Singleton {
    private static Singleton singleton;
    private Singleton() {}

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

解決上面第1種實(shí)現(xiàn)方式的線程不安全問(wèn)題撤奸,做個(gè)線程同步就可以了吠昭,于是就對(duì)getInstance()方法進(jìn)行了線程同步。
缺點(diǎn):效率太低胧瓜。每個(gè)線程在想獲得類的實(shí)例時(shí)候矢棚,執(zhí)行g(shù)etInstance()方法都要進(jìn)行同步。而其實(shí)這個(gè)方法只執(zhí)行一次實(shí)例化代碼就夠了府喳,后面的想獲得該類實(shí)例蒲肋,直接return就行了。方法進(jìn)行同步效率太低要改進(jìn)钝满。

  1. 單次判斷實(shí)例為null兜粘,同步代碼塊生成實(shí)例【不推薦使用】
public class Singleton {
    private static Singleton singleton;
    private Singleton() {}

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

由于第2種實(shí)現(xiàn)方式同步效率太低,所以摒棄同步方法弯蚜,改為同步產(chǎn)生實(shí)例化的的代碼塊孔轴。但是這種同步并不能起到線程同步的作用。跟第1種實(shí)現(xiàn)方式遇到的情形一致熟吏。假如一個(gè)線程進(jìn)入了 if (singleton == null) 判斷語(yǔ)句塊距糖,還未來(lái)得及往下執(zhí)行,另一個(gè)線程也通過(guò)了這個(gè)判斷語(yǔ)句牵寺,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例悍引。

  1. 雙重檢查【推薦使用】
public class Singleton {
    private static volatile Singleton singleton;
    private Singleton() {}

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

Double-Check概念對(duì)于多線程開(kāi)發(fā)者來(lái)說(shuō)不會(huì)陌生。進(jìn)行兩次 if (singleton == null) 檢查帽氓,這樣就可以保證線程安全了趣斤。實(shí)例化代碼只用執(zhí)行一次,后面再次訪問(wèn)時(shí)黎休,判斷 if (singleton == null)浓领,直接return實(shí)例化對(duì)象玉凯。
優(yōu)點(diǎn):線程安全;延遲加載联贩;效率較高漫仆。

  1. 靜態(tài)內(nèi)部類【推薦使用】
public class Singleton {
    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
}

這種方式跟餓漢式方式采用的機(jī)制類似,但又有不同泪幌。兩者都是采用了類裝載的機(jī)制來(lái)保證初始化實(shí)例時(shí)只有一個(gè)線程盲厌。不同的地方在餓漢式方式是只要Singleton類被裝載就會(huì)實(shí)例化,沒(méi)有Lazy-Loading的作用祸泪,而靜態(tài)內(nèi)部類方式在Singleton類被裝載時(shí)并不會(huì)立即實(shí)例化吗浩,而是在需要實(shí)例化時(shí),調(diào)用getInstance方法没隘,才會(huì)裝載SingletonInstance類懂扼,從而完成Singleton的實(shí)例化。
類的靜態(tài)屬性只會(huì)在第一次加載類的時(shí)候初始化右蒲,所以在這里阀湿,JVM幫助我們保證了線程的安全性,在類進(jìn)行初始化時(shí)品嚣,別的線程是無(wú)法進(jìn)入的炕倘。
優(yōu)點(diǎn):避免了線程不安全钧大,延遲加載翰撑,效率高。

6 相關(guān)知識(shí)補(bǔ)充

6.1 關(guān)于延遲初始化(lazy loaded)

原則:“除非絕對(duì)必要啊央,否則就不要延遲初始化”眶诈。
延遲初始化是一把雙刃劍,它降低了初始化類或者創(chuàng)建實(shí)例的開(kāi)銷瓜饥,卻增加了訪問(wèn)被延遲初始化的域的開(kāi)銷逝撬,考慮到延遲初始化的域最終需要初始化的開(kāi)銷以及域的訪問(wèn)開(kāi)銷,延遲初始化實(shí)際上降低了性能乓土。

參考

  1. JAVA設(shè)計(jì)模式總結(jié)之23種設(shè)計(jì)模式
  2. 設(shè)計(jì)模式之禪
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宪潮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子趣苏,更是在濱河造成了極大的恐慌狡相,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件食磕,死亡現(xiàn)場(chǎng)離奇詭異尽棕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)彬伦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén)滔悉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)伊诵,“玉大人,你說(shuō)我怎么就攤上這事回官〔苎纾” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵歉提,是天一觀的道長(zhǎng)浙炼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)唯袄,這世上最難降的妖魔是什么弯屈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮恋拷,結(jié)果婚禮上资厉,老公的妹妹穿的比我還像新娘。我一直安慰自己蔬顾,他們只是感情好宴偿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著诀豁,像睡著了一般窄刘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舷胜,一...
    開(kāi)封第一講書(shū)人閱讀 49,730評(píng)論 1 289
  • 那天娩践,我揣著相機(jī)與錄音,去河邊找鬼烹骨。 笑死翻伺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沮焕。 我是一名探鬼主播吨岭,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼峦树!你這毒婦竟也來(lái)了辣辫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤魁巩,失蹤者是張志新(化名)和其女友劉穎急灭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體歪赢,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡化戳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片点楼。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扫尖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出掠廓,到底是詐尸還是另有隱情换怖,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布蟀瞧,位于F島的核電站沉颂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悦污。R本人自食惡果不足惜铸屉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望切端。 院中可真熱鬧彻坛,春花似錦、人聲如沸踏枣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)茵瀑。三九已至间驮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間马昨,已是汗流浹背竞帽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留偏陪,地道東北人抢呆。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓煮嫌,卻偏偏與公主長(zhǎng)得像笛谦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昌阿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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

  • 前言 本文主要參考 那些年饥脑,我們一起寫(xiě)過(guò)的“單例模式”。 何為單例模式懦冰? 顧名思義灶轰,單例模式就是保證一個(gè)類僅有一個(gè)...
    tandeneck閱讀 2,488評(píng)論 1 8
  • 一.什么是單例模式 單例模式的定義:確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)他的全局訪問(wèn)點(diǎn)刷钢。單例模式是幾個(gè)設(shè)計(jì)模式中...
    Geeks_Liu閱讀 2,216評(píng)論 0 10
  • 單例設(shè)計(jì)模式全解析 在學(xué)習(xí)設(shè)計(jì)模式時(shí)笋颤,單例設(shè)計(jì)模式應(yīng)該是學(xué)習(xí)的第一個(gè)設(shè)計(jì)模式,單例設(shè)計(jì)模式也是“公認(rèn)”最簡(jiǎn)單的設(shè)計(jì)...
    WekingZhang閱讀 290評(píng)論 0 1
  • 聲明:原創(chuàng)作品,轉(zhuǎn)載請(qǐng)注明出處http://www.reibang.com/p/b99e870f4ce0 有的時(shí)...
    蛇發(fā)女妖閱讀 2,834評(píng)論 9 24
  • 1.蝦開(kāi)背去蝦線伴澄,倒入料酒赋除、鹽腌制 2.倒入蛋清、淀粉非凌、十三香举农、燒烤料腌制5分鐘 3.熱鍋熱油,放入蝦仁炸敞嗡,炸至兩...
    ericguo閱讀 574評(píng)論 0 0