設(shè)計模式修煉之路-單例模式

單例模式應(yīng)該是我們平時聽過最多的設(shè)計模式,也是最簡單的設(shè)計模式疆偿,面試的時候也經(jīng)常會被問到,有的面試官還動不動讓你手寫一個單例模式搓幌。我們來一起了解一下單例模式吧杆故。

一、什么是單例模式溉愁?

單例顧名思義就是單個的實例(對象)处铛。單例模式屬于創(chuàng)建型模式,它只涉及到單個的類,不通過new的方式創(chuàng)建對象撤蟆,而是由類本身提供的方法創(chuàng)建和訪問自己的對象奕塑,同時保證對象不會被多次創(chuàng)建,多次訪問該方法時訪問的是同一個對象枫疆。

二爵川、單例模式的優(yōu)缺點

優(yōu)點:
1.有效的減少資源消耗。
2.方便對象狀態(tài)的共享息楔。
3.避免對資源的多重占用(如寫文件操作時文件被多個對象占用)寝贡。
缺點:
1.因為狀態(tài)共享,所以不適合同一個類需要在不同場景保存各自狀態(tài)的情況值依。
2.不能繼承(登記式除外)圃泡,因此無法通過子類擴(kuò)展功能。
3.違反了單一職責(zé)原則愿险。因為實例化自身的職責(zé)是由自身完成的颇蜡。
4.對于保存狀態(tài)的單例對象,如果長時間未調(diào)用辆亏,對象有可能會被回收導(dǎo)致狀態(tài)丟失风秤。

三、單例模式的應(yīng)用場景版本

單例模式的主要應(yīng)用場景如下:
1.對象需要頻繁創(chuàng)建扮叨、銷毀的時候缤弦,例如被spring管理的bean默認(rèn)都是單例模式,如果不是單例模式我們每次http請求的時候就需要創(chuàng)建很多對象彻磁,請求結(jié)束又要銷毀這些對象碍沐。
2.創(chuàng)建對象需要時消耗時間較長或消耗資源較多且使用較頻繁的對象。例如一些從文件讀取數(shù)據(jù)的配置類衷蜓,i/o操作就比較耗時耗資源累提。
3.需要全局共享狀態(tài)或者資源的類,例如線程池磁浇、數(shù)據(jù)庫連接池斋陪、保存了狀態(tài)的工具類等。

四扯夭、單例模式的實現(xiàn)方式

1.懶漢式

懶漢式鳍贾,在系統(tǒng)啟動的時候不會實例化對象,只有在第一次獲取對象時才會創(chuàng)建對象.

1.1線程不安全

這種實現(xiàn)方式適合用于單線程場景交洗,多線程場景會創(chuàng)建多個實例骑科。

public class Singleton1 {

    private static Singleton1 instance;

    private Singleton1() {
        System.out.println("調(diào)用構(gòu)造函數(shù)創(chuàng)建對象");
    }

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

我們創(chuàng)建10個線程調(diào)用getInstance()方法進(jìn)行測試,代碼如下:

public static void main(String[] args) {
        singleton1();
    }

private static void singleton1(){
        List<Thread> threads = new ArrayList<>(10);
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(Singleton1::getInstance));
        }
        threads.forEach(Thread::start);
    }

測試結(jié)果如下:

Task :Test.main()
調(diào)用構(gòu)造函數(shù)創(chuàng)建對象
調(diào)用構(gòu)造函數(shù)創(chuàng)建對象
調(diào)用構(gòu)造函數(shù)創(chuàng)建對象
調(diào)用構(gòu)造函數(shù)創(chuàng)建對象
調(diào)用構(gòu)造函數(shù)創(chuàng)建對象

可以看出構(gòu)造函數(shù)被調(diào)用了多次构拳,也就是說對象被多次實例化咆爽,并未實現(xiàn)單例梁棠。

1.2線程安全

下述方式對getInstance()方法加了鎖,因此是線程安全的斗埂。加鎖會影響效率符糊,適用于getInstance()方法的性能對程序不是很重要的場景。

public class Singleton2 {

    private static Singleton2 instance;

    private Singleton2() {
        System.out.println("調(diào)用構(gòu)造函數(shù)創(chuàng)建對象");
    }

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

我們創(chuàng)建10個線程調(diào)用getInstance()方法進(jìn)行測試呛凶,代碼如下:

 public static void main(String[] args) {
        singleton2();
    }

    private static void singleton2(){
        List<Thread> threads = new ArrayList<>(10);
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(Singleton2::getInstance));
        }
        threads.forEach(Thread::start);
    }

反復(fù)執(zhí)行多次結(jié)果均如下:

Task :Test.main()
調(diào)用構(gòu)造函數(shù)創(chuàng)建對象
可以看出對象只被創(chuàng)建了一次男娄,因此時線程安全的。

2.餓漢式

餓漢式是在類被加載的時候就創(chuàng)建好了對象漾稀,因此是線程安全的模闲,并且沒有加鎖,對不會影響程序效率崭捍,但是因為是在未使用之前加載的尸折,所以會浪費(fèi)一些內(nèi)存。

public class Singleton3 {

    private static Singleton3 instance = new Singleton3();

    private Singleton3() {
        System.out.println("調(diào)用構(gòu)造函數(shù)創(chuàng)建對象");
    }

    public static Singleton3 getInstance() {
        return instance;
    }
}

3.雙重校驗鎖

這種方式采用雙鎖機(jī)制殷蛇,只鎖了創(chuàng)建對象的一部分代碼实夹,因此在多線程情況下依然能保持高性能。下面的例子粒梦,有的童鞋可能覺得和方法上加鎖性能差不多亮航,如果在標(biāo)記1和標(biāo)記4處有一些耗時操作,那么第一個線程在創(chuàng)建對象的時候第二個線程已經(jīng)可以進(jìn)入方法在標(biāo)記1處執(zhí)行耗時操作了匀们,等到第一個線程創(chuàng)建完對象兩個線程又可以同時執(zhí)行標(biāo)記4處的耗時操作塞赂,如果是方法上加鎖第二個線程必須等到第一個線程執(zhí)行完標(biāo)記1和標(biāo)記4處的耗時操作才能再進(jìn)入方法。
標(biāo)記3處的第二個null判斷的意義又是什么呢昼蛀?此處的null判斷非常關(guān)鍵,如果有兩個線程同時執(zhí)行到了標(biāo)記2處圆存,其中一個線程拿到了鎖叼旋,創(chuàng)建了對象,釋放鎖后另一個線程拿到鎖后如果沒有標(biāo)記3處的null判斷沦辙,第二個線程就會再創(chuàng)建一個對象夫植,就無法保證單例。

public class Singleton4 {

    private volatile static Singleton4 instance = new Singleton4();

    private Singleton4() {
        System.out.println("調(diào)用構(gòu)造函數(shù)創(chuàng)建對象");
    }

    public static Singleton4 getInstance() {
        //標(biāo)記1
        if (instance == null){
            //標(biāo)記2
            synchronized (Singleton4.class){
                //標(biāo)記3
                if (instance==null){
                    instance = new Singleton4();
                }
            }
        }
        //標(biāo)記4
        return instance;
    }
}

4.登記式/靜態(tài)內(nèi)部類

這種方式的功效跟雙重校驗鎖一樣油讯,但實現(xiàn)方式更為簡單详民。利用了classloader加載類的原理,保證了初始化Singleton5時只有一個線程陌兑,同時保證了了初始化Singleton5時它的靜態(tài)內(nèi)部類SingletonInner不會被加載沈跨,這樣Singleton5對象(INSTANCE)也就不會被創(chuàng)建。

public class Singleton5 {

    public static class SingletonInner {
        public static final Singleton5 INSTANCE = new Singleton5();
    }

    private Singleton5() {
        System.out.println("調(diào)用構(gòu)造函數(shù)創(chuàng)建對象");
    }

    public static Singleton5 getInstance() {

        return SingletonInner.INSTANCE;
    }
}

5.枚舉

這種方式利用了枚舉類本身的特性兔综,支持序列化機(jī)制可以在反序列化時避免重復(fù)創(chuàng)建對象饿凛,同時還能避免線程同步問題狞玛。但這種方式在實際使用中用的比較少。

public enum  Singleton6 {
    INSTANCE
    //下面可以寫一些方法
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末涧窒,一起剝皮案震驚了整個濱河市心肪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纠吴,老刑警劉巖硬鞍,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異戴已,居然都是意外死亡固该,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門恭陡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹬音,“玉大人,你說我怎么就攤上這事休玩≈” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵拴疤,是天一觀的道長永部。 經(jīng)常有香客問我,道長呐矾,這世上最難降的妖魔是什么苔埋? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蜒犯,結(jié)果婚禮上组橄,老公的妹妹穿的比我還像新娘。我一直安慰自己罚随,他們只是感情好玉工,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淘菩,像睡著了一般遵班。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上潮改,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天狭郑,我揣著相機(jī)與錄音,去河邊找鬼汇在。 笑死翰萨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的糕殉。 我是一名探鬼主播缨历,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼以蕴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辛孵?” 一聲冷哼從身側(cè)響起丛肮,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎魄缚,沒想到半個月后宝与,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡冶匹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年习劫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚼隘。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡诽里,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出飞蛹,到底是詐尸還是另有隱情谤狡,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布卧檐,位于F島的核電站墓懂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏霉囚。R本人自食惡果不足惜捕仔,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盈罐。 院中可真熱鬧榜跌,春花似錦、人聲如沸盅粪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湾揽。三九已至,卻和暖如春笼吟,著一層夾襖步出監(jiān)牢的瞬間库物,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工贷帮, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留戚揭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓撵枢,卻偏偏與公主長得像民晒,于是被迫代替她去往敵國和親精居。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

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