9遍搞、六種單例模式

概述

單例模式算是我接觸設(shè)計(jì)模式這種思想所學(xué)習(xí)的第一個(gè)設(shè)計(jì)模式。記得剛?cè)胄袝r(shí)面試器腋,面試官總是會(huì)讓寫一種單例模式的實(shí)現(xiàn)溪猿。這篇文章主要是來總結(jié)一下單例模式的幾種實(shí)現(xiàn)以及每一種實(shí)現(xiàn)的優(yōu)缺點(diǎn),旨在領(lǐng)會(huì)每一種寫法纫塌,真正明白他們的區(qū)別诊县,免得以后尷尬。Mark措左。

定義

按照設(shè)計(jì)模式中的定義依痊,Singleton模式的用途是"ensure a class has only one instance, and provide a global
point of access to it"
(確保每個(gè)類只有一個(gè)實(shí)例,并提供它的全局訪問點(diǎn))
故名思義怎披,就是這個(gè)類在當(dāng)次整個(gè)系統(tǒng)中只存在一個(gè)實(shí)例胸嘁,所有的訪問必須通過這個(gè)唯一的實(shí)例來進(jìn)行調(diào)用

一、懶漢式

之所以稱為懶漢式凉逛,是基于這個(gè)類的實(shí)例是否能夠按需加載性宏,也就是懶加載。

代碼實(shí)現(xiàn)
public class LazySingleton {
    private static LazySingleton lazySingleton;
    privite LazySingleton(){}
    public static LazySingleton  getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

這種是最基本的實(shí)現(xiàn)鱼炒,但是這種方式在多線程并發(fā)中是有問題的衔沼,不是唯一的實(shí)例。
假如A/B兩個(gè)線程第一次同時(shí)訪問這個(gè)getInstance方法獲得實(shí)例昔瞧,然而這個(gè)條件(instance == null)有可能同時(shí)都成立(并發(fā)執(zhí)行)指蚁,那么線程A和線程B會(huì)分別獲得一個(gè)類的對(duì)象,這樣的話自晰,這個(gè)類的單例就失去意義了凝化。

策略評(píng)價(jià):
優(yōu)點(diǎn)是可以實(shí)現(xiàn)延遲加載。在類初次加載的時(shí)候酬荞,由于只是聲明了這個(gè)靜態(tài)的對(duì)象搓劫,但不會(huì)自動(dòng)初始化 instance對(duì)象,所以稱為懶漢式
缺點(diǎn)是線程不安全混巧。多線程并發(fā)無(wú)法保證唯一的實(shí)例
改進(jìn)策略:需要保證線程安全

二枪向、餓漢式

public class HungrySingleton {
    private static final HungrySingleton hungrySingleton = new HungrySingleton ();
    privite HungrySingleton(){}
    public static HungrySingleton getInstance(){
        return hungrySingleton ;
    }
}

策略評(píng)價(jià):
線程安全。利用了類的加載機(jī)制咧党,加載初始化靜態(tài)變量秘蛔,且被只會(huì)執(zhí)行一次,且JVM會(huì)利用鎖來同步多個(gè)線程對(duì)同一個(gè)類的初始化。這樣就保證了構(gòu)造方法只會(huì)調(diào)用一次深员。
不能懶加載负蠕。無(wú)論是否使用都會(huì)去初始化實(shí)例。
改進(jìn)策略:需要懶加載倦畅,請(qǐng)看懶漢線程安全式
注意到一點(diǎn)遮糖,懶漢模式在申明lazySingleton 的時(shí)候沒有加final關(guān)鍵字,但是餓漢模式加了叠赐。那餓漢模式為什么要加final欲账,什么時(shí)候加?
final關(guān)鍵詞是代表此變量一經(jīng)賦值芭概,其指向的內(nèi)存引用地址將不會(huì)再改變敬惦。加final也僅僅是表示類加載的時(shí)候就初始化對(duì)象了。比不加載final要早一點(diǎn)谈山。
如果存在釋放資源的情況下俄删,就不能加final修飾了,釋放資源之后奏路,如果需要重新使用這個(gè)單例畴椰,就必須存在重新初始化的過程,而final定義的常量是不能重新賦值的鸽粉,所以不能加final斜脂,對(duì)于不需要釋放資源的情況,可以加final
總而言之触机,要不要加final修飾帚戳,可以根據(jù)情況而定。
懶漢模式為什么不加final儡首,是因?yàn)楸籪inal修飾的變量需要直接賦值片任,或者在靜態(tài)代碼塊中賦值,這樣就不是懶加載模式了

三蔬胯、懶漢線程安全式

public class LazySafetySingleton {
    private static LazySafetySingleton lazySafeSingleton;
    privite LazySafetySingleton (){}
    public static synchronized LazySafetySingleton getInstance(){
        if(lazySafeSingleton== null){
            lazySafeSingleton= new LazySafetySingleton ();
        }
        return lazySafeSingleton;
    }
}
public class LazySafetySingleton{
    private static LazySafetySingleton lazySafeSingleton;
    privite LazySafetySingleton (){}
    public static LazySafetySingleton getInstance() {
        synchronized (LazySafetySingleton.class) {
            if (lazySafeSingleton == null) {
                lazySafeSingleton = new LazySafetySingleton();
            }
        }
        return lazySafeSingleton ;
    }
}

策略評(píng)價(jià):

線程安全对供。使用了synchronized同步鎖。
懶加載氛濒。不會(huì)在類加載就初始化产场。
顯然這種同步性能很低。由于使用了同步鎖舞竿,所以每次調(diào)用getInstance都會(huì)進(jìn)行同步京景。其實(shí)我們只想第一次(instance == null)進(jìn)行同步,初始化成功后骗奖,以后每次都直接返回就行了确徙。
改進(jìn)策略:DCL

四靡菇、double-check-locking

public class DclSingleton {
    private volatile static DclSingleton  dlcSingleton;
    privite DclSingleton (){}
    public static DclSingleton  getInstance() {
        if(dlcSingleton== null){
            synchronized (DclSingleton .class){
                if(dlcSingleton == null){
                    dlcSingleton = new DclSingleton();
                }
            }
        }
        return dlcSingleton ;
    }
}

這種策略看似解決了每次都需要同步的問題,但是由于 標(biāo)記4處 instance = new DclSingleton(); 這個(gè)初始化是非原子性的操作米愿。就是說這個(gè)在JVM中可能會(huì)分成幾步執(zhí)行,那么就會(huì)存在指令重排序的問題,所以需要繼續(xù)改進(jìn) 聲明處添加 volatile 關(guān)鍵字鼻吮。
改進(jìn)后育苟,懶加載
線程安全

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

public class StaticInnerSingleton {
    privite StaticInnerSingleton(){}
    public static StaticInnerSingleton getinstance(){
        return SingletonHolder.staticInnerSingleton;
    }
    public static class SingletonHolder{
        private static final StaticInnerSingleton staticInnerSingleton = new StaticInnerSingleton();
    }
}

策略評(píng)價(jià):
這種靜態(tài)內(nèi)部類的實(shí)現(xiàn)椎木,主要是運(yùn)用類的加載機(jī)制來保證線程的安全(原因見懶漢式的線程安全優(yōu)點(diǎn))违柏。同時(shí)這個(gè)內(nèi)部類與外部類沒有綁定關(guān)系,而且只有外部類調(diào)用的時(shí)候才會(huì)加載香椎,所以能做到懶加載漱竖。
但是如果實(shí)例化的時(shí)候需要傳參就很不方便了,比如需要傳context畜伐。所以不需要傳參的時(shí)候建議使用該方法

六馍惹、枚舉

public enum EnumSingleton {
    INSTANCE;

    public void doSomeThing() {
    //在此處進(jìn)行實(shí)例化對(duì)象
    }

}

策略評(píng)價(jià):枚舉實(shí)現(xiàn)是目前比較建議的方法,和他獲取單例的方法比起來玛界,有兩個(gè)明顯的優(yōu)勢(shì):

  • 避免反射攻擊万矾,其他方式實(shí)現(xiàn)的單例都可以通過反射拿到構(gòu)造器,通過構(gòu)造器獲取實(shí)例慎框。但獲取的實(shí)例和通過正常方式獲取的實(shí)例不是同一個(gè)對(duì)象良狈。枚舉單例模式無(wú)法通過反射獲取實(shí)例,因?yàn)镃onstructor類的newInstance方法源碼中有判斷笨枯,要實(shí)例化的類是否是枚舉薪丁,如果不是,才能實(shí)例化(14行)
1 public T newInstance(Object ... initargs)
2        throws InstantiationException, IllegalAccessException,
3                IllegalArgumentException, InvocationTargetException
4      {
5         if (!override) {
6              if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
7                  Class<?> caller = Reflection.getCallerClass();
8                 checkAccess(caller, clazz, null, modifiers);
9            }
10         }
11         if ((clazz.getModifiers() & Modifier.ENUM) != 0)
12            throw new IllegalArgumentException("Cannot reflectively create enum objects");
13         ConstructorAccessor ca = constructorAccessor;   // read volatile
14        if (ca == null) {
15             ca = acquireConstructorAccessor();
16        }
         @SuppressWarnings("unchecked")
         T inst = (T) ca.newInstance(initargs);
         return inst;
     }
  • 避免序列化
    傳統(tǒng)單例存在的另外一個(gè)問題是一旦你實(shí)現(xiàn)了序列化接口馅精,那么它們不再保持單例了严嗜,因?yàn)閞eadObject()方法一直返回一個(gè)新的對(duì)象就像java的構(gòu)造方法一樣。但枚舉實(shí)現(xiàn)的單例洲敢,即使序列化阻问,獲取的對(duì)象依然保持單例。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沦疾,一起剝皮案震驚了整個(gè)濱河市称近,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哮塞,老刑警劉巖刨秆,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異忆畅,居然都是意外死亡衡未,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缓醋,“玉大人如失,你說我怎么就攤上這事∷土唬” “怎么了褪贵?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)抗俄。 經(jīng)常有香客問我脆丁,道長(zhǎng),這世上最難降的妖魔是什么动雹? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任槽卫,我火速辦了婚禮,結(jié)果婚禮上胰蝠,老公的妹妹穿的比我還像新娘歼培。我一直安慰自己,他們只是感情好茸塞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布丐怯。 她就那樣靜靜地躺著,像睡著了一般翔横。 火紅的嫁衣襯著肌膚如雪读跷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天禾唁,我揣著相機(jī)與錄音效览,去河邊找鬼。 笑死荡短,一個(gè)胖子當(dāng)著我的面吹牛丐枉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掘托,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼瘦锹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了闪盔?” 一聲冷哼從身側(cè)響起弯院,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泪掀,沒想到半個(gè)月后听绳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡异赫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年椅挣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了头岔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鼠证,死狀恐怖峡竣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情量九,我是刑警寧澤适掰,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站娩鹉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏稚伍。R本人自食惡果不足惜弯予,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望个曙。 院中可真熱鬧锈嫩,春花似錦、人聲如沸垦搬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)猴贰。三九已至对雪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間米绕,已是汗流浹背瑟捣。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留栅干,地道東北人迈套。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像碱鳞,于是被迫代替她去往敵國(guó)和親桑李。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

  • 前言 本文主要參考 那些年窿给,我們一起寫過的“單例模式”贵白。 何為單例模式? 顧名思義崩泡,單例模式就是保證一個(gè)類僅有一個(gè)...
    tandeneck閱讀 2,515評(píng)論 1 8
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,103評(píng)論 1 32
  • 1 場(chǎng)景問題# 1.1 讀取配置文件的內(nèi)容## 考慮這樣一個(gè)應(yīng)用戒洼,讀取配置文件的內(nèi)容。 很多應(yīng)用項(xiàng)目允华,都有與應(yīng)用相...
    七寸知架構(gòu)閱讀 6,773評(píng)論 12 68
  • 2016.10.19-010 正文:當(dāng)你無(wú)法幫助別人決定時(shí)圈浇,借力使力的回應(yīng)會(huì)是一個(gè)很棒的技巧寥掐。你會(huì)發(fā)現(xiàn)其實(shí)你...
    李向姿閱讀 154評(píng)論 2 0
  • 荊軻,戰(zhàn)國(guó)末期衛(wèi)國(guó)人磷蜀,在田光眼中召耘,他知道荊軻的過人之處,所以他對(duì)荊軻特別好褐隆。田光是燕國(guó)有名的大俠污它,文武雙全。當(dāng)時(shí)庶弃,...
    MORON_2e12閱讀 343評(píng)論 0 0