設(shè)計(jì)模式-單例模式(Singleton)

單例模式(Singleton)

單例對(duì)象(Singleton)是一種常用的設(shè)計(jì)模式嫉父。在Java應(yīng)用中,單例對(duì)象能保證在一個(gè)JVM中,該對(duì)象只有一個(gè)實(shí)例存在犹菱。

這樣的模式有幾個(gè)好處:

  1. 某些類創(chuàng)建比較頻繁,對(duì)于一些大型的對(duì)象吮炕,這是一筆很大的系統(tǒng)開銷腊脱。

  2. 省去了new操作符,降低了系統(tǒng)內(nèi)存的使用頻率龙亲,減輕GC壓力陕凹。

  3. 有些類如交易所的核心交易引擎,控制著交易流程鳄炉,如果該類可以創(chuàng)建多個(gè)的話杜耙,系統(tǒng)完全亂了。(比如一個(gè)軍隊(duì)出現(xiàn)了多個(gè)司令員同時(shí)指揮拂盯,肯定會(huì)亂成一團(tuán))佑女,所以只有使用單例模式,才能保證核心交易服務(wù)器獨(dú)立控制整個(gè)流程谈竿。

首先我們寫一個(gè)簡(jiǎn)單的單例類:

public class Singleton {  
  
    /* 持有私有靜態(tài)實(shí)例团驱,防止被引用,此處賦值為null空凸,目的是實(shí)現(xiàn)延遲加載 */  
    private static Singleton instance = null;  
  
    /* 私有構(gòu)造方法嚎花,防止被實(shí)例化 */  
    private Singleton() {  
    }  
  
    /* 靜態(tài)工程方法,創(chuàng)建實(shí)例 */  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
  
    /* 如果該對(duì)象被用于序列化劫恒,可以保證對(duì)象在序列化前后保持一致 */  
    public Object readResolve() {  
        return instance;  
    }  
}  

這個(gè)類可以滿足基本要求贩幻,但是,像這樣毫無線程安全保護(hù)的類两嘴,如果我們把它放入多線程的環(huán)境下丛楚,肯定就會(huì)出現(xiàn)問題了,如何解決憔辫?

我們首先會(huì)想到對(duì)getInstance方法加synchronized關(guān)鍵字趣些,如下:

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

但是,synchronized關(guān)鍵字鎖住的是這個(gè)對(duì)象贰您,這樣的用法坏平,在性能上會(huì)有所下降拢操,因?yàn)槊看握{(diào)用getInstance(),都要對(duì)對(duì)象上鎖舶替,事實(shí)上令境,只有在第一次創(chuàng)建對(duì)象的時(shí)候需要加鎖,之后就不需要了顾瞪,所以舔庶,這個(gè)地方需要改進(jìn)。我們改成下面這個(gè):

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

似乎解決了之前提到的問題陈醒,將synchronized關(guān)鍵字加在了內(nèi)部惕橙,也就是說當(dāng)調(diào)用的時(shí)候是不需要加鎖的,只有在instance為null钉跷,并創(chuàng)建對(duì)象的時(shí)候才需要加鎖弥鹦,性能有一定的提升。

但是爷辙,這樣的情況彬坏,還是有可能有問題的,看下面的情況:

在Java指令中創(chuàng)建對(duì)象和賦值操作是分開進(jìn)行的犬钢,也就是說instance = new Singleton();語(yǔ)句是分兩步執(zhí)行的苍鲜。但是JVM并不保證這兩個(gè)操作的先后順序,也就是說有可能JVM會(huì)為新的Singleton實(shí)例分配空間玷犹,然后直接賦值給instance成員混滔,然后再去初始化這個(gè)Singleton實(shí)例。這樣就可能出錯(cuò)了歹颓,我們以A坯屿、B兩個(gè)線程為例:

  1. A、B線程同時(shí)進(jìn)入了第一個(gè)if判斷

  2. A首先進(jìn)入synchronized塊巍扛,由于instance為null领跛,所以它執(zhí)行instance = new Singleton();

  3. 由于JVM內(nèi)部的優(yōu)化機(jī)制,JVM先畫出了一些分配給Singleton實(shí)例的空白內(nèi)存撤奸,并賦值給instance成員(注意此時(shí)JVM沒有開始初始化這個(gè)實(shí)例)吠昭,然后A離開了synchronized塊。

  4. B進(jìn)入synchronized塊胧瓜,由于instance此時(shí)不是null矢棚,因此它馬上離開了synchronized塊并將結(jié)果返回給調(diào)用該方法的程序。

  5. 此時(shí)B線程打算使用Singleton實(shí)例府喳,卻發(fā)現(xiàn)它沒有被初始化蒲肋,于是錯(cuò)誤發(fā)生了。

所以程序還是有可能發(fā)生錯(cuò)誤,其實(shí)程序在運(yùn)行過程是很復(fù)雜的兜粘,從這點(diǎn)我們就可以看出申窘,尤其是在寫多線程環(huán)境下的程序更有難度,有挑戰(zhàn)性孔轴。

我們對(duì)該程序做進(jìn)一步優(yōu)化:

private static class SingletonFactory{           
     private static Singleton instance = new Singleton();           
}           

public static Singleton getInstance(){           
     return SingletonFactory.instance;           
}   

實(shí)際情況是剃法,單例模式使用內(nèi)部類來維護(hù)單例的實(shí)現(xiàn),JVM內(nèi)部的機(jī)制能夠保證當(dāng)一個(gè)類被加載的時(shí)候距糖,這個(gè)類的加載過程是線程互斥的玄窝。這樣當(dāng)我們第一次調(diào)用getInstance的時(shí)候,JVM能夠幫我們保證instance只被創(chuàng)建一次悍引,并且會(huì)保證把賦值給instance的內(nèi)存初始化完畢,這樣我們就不用擔(dān)心上面的問題帽氓。同時(shí)該方法也只會(huì)在第一次調(diào)用的時(shí)候使用互斥機(jī)制趣斤,這樣就解決了低性能問題。

這樣我們暫時(shí)總結(jié)一個(gè)完美的單例模式:

public class Singleton {  
  
    /* 私有構(gòu)造方法黎休,防止被實(shí)例化 */  
    private Singleton() {  
    }  
  
    /* 此處使用一個(gè)內(nèi)部類來維護(hù)單例 */  
    private static class SingletonFactory {  
        private static Singleton instance = new Singleton();  
    }  
  
    /* 獲取實(shí)例 */  
    public static Singleton getInstance() {  
        return SingletonFactory.instance;  
    }  
  
    /* 如果該對(duì)象被用于序列化浓领,可以保證對(duì)象在序列化前后保持一致 */  
    public Object readResolve() {  
        return getInstance();  
    }  
}  

其實(shí)說它完美,也不一定势腮,如果在構(gòu)造函數(shù)中拋出異常联贩,實(shí)例將永遠(yuǎn)得不到創(chuàng)建,也會(huì)出錯(cuò)捎拯。所以說泪幌,十分完美的東西是沒有的,我們只能根據(jù)實(shí)際情況署照,選擇最適合自己應(yīng)用場(chǎng)景的實(shí)現(xiàn)方法祸泪。也有人這樣實(shí)現(xiàn):因?yàn)槲覀冎恍枰趧?chuàng)建類的時(shí)候進(jìn)行同步,所以只要將創(chuàng)建和getInstance()分開建芙,單獨(dú)為創(chuàng)建加synchronized關(guān)鍵字没隘,也是可以的:

public class SingletonTest {  
  
    private static SingletonTest instance = null;  
  
    private SingletonTest() {  
    }  
  
    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new SingletonTest();  
        }  
    }  
  
    public static SingletonTest getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  
}  

考慮性能的話,整個(gè)程序只需創(chuàng)建一次實(shí)例禁荸,所以性能也不會(huì)有什么影響右蒲。

補(bǔ)充:采用"影子實(shí)例"的辦法為單例對(duì)象的屬性同步更新

public class SingletonTest {  
  
    private static SingletonTest instance = null;  
    private Vector properties = null;  
  
    public Vector getProperties() {  
        return properties;  
    }  
  
    private SingletonTest() {  
    }  
  
    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new SingletonTest();  
        }  
    }  
  
    public static SingletonTest getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  
  
    public void updateProperties() {  
        SingletonTest shadow = new SingletonTest();  
        properties = shadow.getProperties();  
    }  
}  

通過單例模式的學(xué)習(xí)告訴我們:

  1. 單例模式理解起來簡(jiǎn)單,但是具體實(shí)現(xiàn)起來還是有一定的難度赶熟。

  2. synchronized關(guān)鍵字鎖定的是對(duì)象瑰妄,在用的時(shí)候,一定要在恰當(dāng)?shù)牡胤绞褂茫ㄗ⒁庑枰褂面i的對(duì)象和過程钧大,可能有的時(shí)候并不是整個(gè)對(duì)象及整個(gè)過程都需要鎖)翰撑。

到這兒,單例模式基本已經(jīng)講完了,結(jié)尾處眶诈,筆者突然想到另一個(gè)問題涨醋,就是采用類的靜態(tài)方法,實(shí)現(xiàn)單例模式的效果逝撬,也是可行的浴骂,此處二者有什么不同?

首先宪潮,靜態(tài)類不能實(shí)現(xiàn)接口溯警。(從類的角度說是可以的,但是那樣就破壞了靜態(tài)了狡相。因?yàn)榻涌谥胁辉试S有static修飾的方法梯轻,所以即使實(shí)現(xiàn)了也是非靜態(tài)的)

其次,單例可以被延遲初始化尽棕,靜態(tài)類一般在第一次加載是初始化喳挑。之所以延遲加載,是因?yàn)橛行╊惐容^龐大滔悉,所以延遲加載有助于提升性能伊诵。

再次,單例類可以被繼承回官,他的方法可以被覆寫曹宴。但是靜態(tài)類內(nèi)部方法都是static,無法被覆寫歉提。

最后一點(diǎn)笛坦,單例類比較靈活,畢竟從實(shí)現(xiàn)上只是一個(gè)普通的Java類唯袄,只要滿足單例的基本需求弯屈,你可以在里面隨心所欲的實(shí)現(xiàn)一些其它功能,但是靜態(tài)類不行恋拷。

從上面這些概括中资厉,基本可以看出二者的區(qū)別,但是蔬顾,從另一方面講宴偿,我們上面最后實(shí)現(xiàn)的那個(gè)單例模式,內(nèi)部就是用一個(gè)靜態(tài)類來實(shí)現(xiàn)的诀豁,所以窄刘,二者有很大的關(guān)聯(lián),只是我們考慮問題的層面不同罷了舷胜。兩種思想的結(jié)合娩践,才能造就出完美的解決方案,就像HashMap采用數(shù)組+鏈表來實(shí)現(xiàn)一樣,其實(shí)生活中很多事情都是這樣翻伺,單用不同的方法來處理問題材泄,總是有優(yōu)點(diǎn)也有缺點(diǎn),最完美的方法是吨岭,結(jié)合各個(gè)方法的優(yōu)點(diǎn)拉宗,才能最好的解決問題!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辣辫,一起剝皮案震驚了整個(gè)濱河市旦事,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌急灭,老刑警劉巖姐浮,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異化戳,居然都是意外死亡单料,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門点楼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人白对,你說我怎么就攤上這事掠廓。” “怎么了甩恼?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蟀瞧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我条摸,道長(zhǎng)悦污,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任钉蒲,我火速辦了婚禮切端,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘顷啼。我一直安慰自己踏枣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布钙蒙。 她就那樣靜靜地躺著茵瀑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躬厌。 梳的紋絲不亂的頭發(fā)上马昨,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼鸿捧。 笑死屹篓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笛谦。 我是一名探鬼主播抱虐,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼饥脑!你這毒婦竟也來了恳邀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤灶轰,失蹤者是張志新(化名)和其女友劉穎谣沸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笋颤,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乳附,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伴澄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赋除。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖非凌,靈堂內(nèi)的尸體忽然破棺而出举农,到底是詐尸還是另有隱情,我是刑警寧澤敞嗡,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布颁糟,位于F島的核電站,受9級(jí)特大地震影響喉悴,放射性物質(zhì)發(fā)生泄漏棱貌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一箕肃、第九天 我趴在偏房一處隱蔽的房頂上張望婚脱。 院中可真熱鬧,春花似錦突雪、人聲如沸起惕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惹想。三九已至,卻和暖如春督函,著一層夾襖步出監(jiān)牢的瞬間嘀粱,已是汗流浹背激挪。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锋叨,地道東北人垄分。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像娃磺,于是被迫代替她去往敵國(guó)和親薄湿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

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