設(shè)計模式 | 詳解單例模式(餓漢式岸浑、懶漢式、靜態(tài)內(nèi)部類瑰步、枚舉矢洲、JDK源碼剖析)

一、單例模式介紹

所謂類的單例設(shè)計模式缩焦,就是采取一定的方法保證在整個的軟件系統(tǒng)中读虏,對于某個類只能存在一個對象實例责静,并且該類只提供一個取得其對象實例的方法(靜態(tài)方法)。

比如Hibernate的SessionFactory盖桥,它充當(dāng)數(shù)據(jù)存儲源的代理灾螃,并負(fù)責(zé)創(chuàng)建Session對象。SessionFactory并不是輕量級的揩徊,一般情況下腰鬼,一個項目通常只需要一個SessionFactory就夠,這時就會使用到單例模式塑荒。

二熄赡、單例模式的七種方式

單例模式有以下七種方式

  • 餓漢式(靜態(tài)常量)
  • 餓漢式(靜態(tài)代碼塊)
  • 懶漢式(線程不安全)
  • 懶漢式(線程安全,同步方法)
  • 懶漢式之雙重檢查(線程安全袜炕,同步代碼塊)
  • 靜態(tài)內(nèi)部類
  • 枚舉

1本谜、餓漢式(靜態(tài)常量)

步驟如下:

  • 構(gòu)造器私有化(防止new)
  • 在類的內(nèi)部創(chuàng)建對象
  • 向外暴露一個靜態(tài)的公共方法(getInstance)
package com.cxc.singleton.type1;

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        Singleton singleton1 = Singleton.getInstance();
        System.out.println(singleton == singleton1);
    }
}

/**
 * 餓漢式(靜態(tài)變量)
 */
class Singleton{
    //構(gòu)造器私有化
    private Singleton(){}

    //在類內(nèi)部創(chuàng)建對象實例
    private final static Singleton instance = new Singleton();

    //提供一個公有的靜態(tài)方法,返回實例對象
    public static Singleton getInstance(){
        return instance;
    }
}

這種方式的優(yōu)點是寫法簡單偎窘,在類裝載的時候就完成了實例化,避免了線程同步問題溜在。
而缺點有以下:

  • 在類裝載的時候就完成實例化陌知,沒有達(dá)到懶加載(Lazy Loading)的效果。如果從始至終從未使用過這個實力掖肋,則會造成內(nèi)存的浪費仆葡。
  • 這種方式基于classloader機(jī)制避免了多線程的同步問題,不過志笼,instance在類裝載時就實例化沿盅,在單例模式中大多數(shù)都是調(diào)用getInstance方法,但是導(dǎo)致類裝載的原因有很多種纫溃,因此不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載腰涧,這時候切換instance就沒有達(dá)到懶加載的效果。
  • 這種單例模式可用紊浩,但是可能造成內(nèi)存浪費窖铡。

2、餓漢式(靜態(tài)代碼塊)

與第一種方式一樣坊谁,僅僅只是將實例化對象放入一個靜態(tài)代碼塊而已费彼。

package com.cxc.singleton.type2;

public class SingletonTest02 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        Singleton singleton1 = Singleton.getInstance();
        System.out.println(singleton == singleton1);
    }
}

/**
 * 餓漢式(靜態(tài)代碼塊)
 */
class Singleton{

    //構(gòu)造器私有化
    private Singleton(){}

    //對象實例
    private static Singleton instance ;

    //在靜態(tài)代碼塊中實例化對象
    static{
        instance = new Singleton();
    }

    //提供一個公有的靜態(tài)方法,返回實例對象
    public static Singleton getInstance(){
        return instance;
    }
}

3口芍、懶漢式(線程不安全)

先不實例化對象箍铲,等到需要用到的時候再去實例化:

package com.cxc.singleton.type3;

public class SingletonTest03 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton);
    }
}

/**
 * 懶漢式(線程不安全)
 */
class Singleton{
    private static Singleton instance;

    private Singleton(){}

    //提供一個靜態(tài)的公有方法,當(dāng)使用到該方法時鬓椭,才去實例化
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

優(yōu)缺點:

  • 起到了懶加載的效果颠猴,但是只能在單線程下使用聋庵。
  • 如果在多線程下,一個線程進(jìn)入了if(singleton == null)判斷語句塊芙粱,還未來得及往下執(zhí)行祭玉,另一個線程也通過了這個判斷語句,這時就會產(chǎn)生多個實例春畔。所以在多線程環(huán)境下不可使用這種方式脱货。

4、懶漢式(線程安全律姨,同步方法)

在方法上加入synchronized同步關(guān)鍵字:

package com.cxc.singleton.type4;

public class SingletonTest04 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton);
    }
}

/**
 * 懶漢式(線程安全,同步方法)
 */
class Singleton{
    private static Singleton instance;

    private Singleton(){}

    //提供一個靜態(tài)的公有方法振峻,加入了同步處理的代碼,解決線程安全問題
    public static synchronized Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

優(yōu)缺點:

  • 解決了線程不安全問題择份。
  • 效率太低了扣孟,每個線程在想獲得類的實例的時候,執(zhí)行getInstance()方法都要進(jìn)行同步荣赶,而其實這個方法只執(zhí)行一次實例化代碼就夠了凤价,后面的想獲得該類實例,直接return就行了拔创,方法進(jìn)行同步效率太低利诺。

5、懶漢式之雙重檢查(線程安全剩燥,同步代碼塊)

在方法里面先判空后慢逾,再使用synchronized關(guān)鍵字的同步代碼塊(鎖的是類),如果再一次判空(這里必須雙重判空灭红,不然會有線程安全問題)

package com.cxc.singleton.type5;

public class SingletonTest05 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton);
    }
}

/**
 * 懶漢式(線程安全,同步代碼塊)
 */
class Singleton{
    private static volatile Singleton instance;

    private Singleton(){}

    //提供一個靜態(tài)的公有方法
    public static Singleton getInstance(){
        if (instance == null){
            synchronized(Singleton.class){
                //雙重判定
                if (instance == null){
                    instance = new Singleton();
                }
            }

        }
        return instance;
    }
}

對象加上了volatile關(guān)鍵字是為了保證變量的可見性侣滩,防止指令重排序

6变擒、靜態(tài)內(nèi)部類

這種方式利用到了靜態(tài)內(nèi)部類的特性:

靜態(tài)內(nèi)部類在外部類裝載時并不是立即被實例化君珠,而是需要使用到靜態(tài)內(nèi)部類時才會被加載。

package com.cxc.singleton.type6;

public class SingletonTest06 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton);
    }
}

/**
 * 靜態(tài)內(nèi)部類
 */
class Singleton{
    private static volatile Singleton instance;

    private Singleton(){}

    //寫一個靜態(tài)內(nèi)部類赁项,該類中有一個靜態(tài)屬性
    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一個靜態(tài)的公有方法
    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

優(yōu)缺點:

  • 這種方式采用了類裝載的機(jī)制來保證初始化實例時只有一個線程葛躏。
  • 靜態(tài)內(nèi)部類這種方式在Singleton類被裝載時并不是立即實例化,而是在需要實例化時悠菜,調(diào)用getInstance方法用到了內(nèi)部類舰攒,才會裝載SingletonInstance類,從而完成Singleton的實例化悔醋。
  • 類的靜態(tài)屬性只會在第一次加載類的時候初始化摩窃,所以在這里,JVM幫助我們保證了線程的安全性,在類進(jìn)行初始化時猾愿,別的線程是無法進(jìn)入的鹦聪。
  • 避免了線程不安全,利用靜態(tài)內(nèi)部類特點實現(xiàn)延遲加載蒂秘,效率高泽本。極力推薦。

7姻僧、枚舉

package com.cxc.singleton.type7;

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

/**
 * 使用枚舉可以實現(xiàn)單例模式
 */
enum Singleton{
    INSTANCE;

    public void sayOk(){
        System.out.println("ok~");
    }
}

三规丽、單例模式在JDK應(yīng)用的源碼分析

在JDK的java.lang.Runtime類中就用到了單例模式,Runtime的部分源碼如下:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    ......
}

Runtime類定義了currentRuntime對象撇贺,然后直接實例化赌莺,對其構(gòu)造方法私有化。這是屬于餓漢式單例模式松嘶。

四艘狭、單例模式注意事項和細(xì)節(jié)說明

  • 單例模式保證了系統(tǒng)內(nèi)存中該類只存在一個對象,節(jié)省了系統(tǒng)資源翠订,對于一些需要頻繁創(chuàng)建銷毀的對象巢音,使用單例模式可以提高系統(tǒng)性能。
  • 當(dāng)想實例化一個單例類的時候蕴轨,必須要記住使用相應(yīng)的獲取對象的方法港谊,而不是使用new
  • 單例模式使用的場景:需要頻繁的進(jìn)行創(chuàng)建和銷毀的對象橙弱、創(chuàng)建對象時耗時過多或耗費資源過多(即重量級對象),但又經(jīng)常用到的對象燥狰、工具類對象棘脐、頻繁訪問數(shù)據(jù)庫或文件的對象(比如數(shù)據(jù)源、session工廠等)龙致。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛀缝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子目代,更是在濱河造成了極大的恐慌屈梁,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榛了,死亡現(xiàn)場離奇詭異在讶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)霜大,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門构哺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事曙强〔泄眨” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵碟嘴,是天一觀的道長溪食。 經(jīng)常有香客問我娜扇,道長错沃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任袱衷,我火速辦了婚禮捎废,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘致燥。我一直安慰自己嫌蚤,他們只是感情好续捂,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布牙瓢。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滓彰。 梳的紋絲不亂的頭發(fā)上控妻,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音找蜜,去河邊找鬼饼暑。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弓叛。 我是一名探鬼主播彰居,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼撰筷!你這毒婦竟也來了陈惰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤毕籽,失蹤者是張志新(化名)和其女友劉穎抬闯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體关筒,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡溶握,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒸播。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睡榆。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖袍榆,靈堂內(nèi)的尸體忽然破棺而出胀屿,到底是詐尸還是另有隱情,我是刑警寧澤包雀,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布宿崭,位于F島的核電站,受9級特大地震影響才写,放射性物質(zhì)發(fā)生泄漏葡兑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一赞草、第九天 我趴在偏房一處隱蔽的房頂上張望铁孵。 院中可真熱鬧,春花似錦房资、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至暑始,卻和暖如春搭独,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背较坛。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工栈暇, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留搞疗,地道東北人娱仔。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓配椭,卻偏偏與公主長得像虫溜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子股缸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359