設(shè)計(jì)模式筆記02——單例模式

設(shè)計(jì)模式筆記01——設(shè)計(jì)原則

介紹

自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。這個(gè)類稱為單例類
單例只有一個(gè)實(shí)例對(duì)象
單例自己創(chuàng)建自己實(shí)例,構(gòu)造函數(shù)為私有
單例提供唯一的實(shí)例給外部引用

實(shí)現(xiàn)方式

單例模式實(shí)現(xiàn)由很多種形式薯蝎,我們并不進(jìn)行列舉,直說一些比較常用的囊蓝。單例模式最常見的莫過于懶漢式和餓漢式了邪媳,這也是最簡單的兩種實(shí)現(xiàn)方式,其他的實(shí)現(xiàn)方式還有雙重檢查加鎖檬洞、Lazy initialization holder class模式狸膏、枚舉等

餓漢式

這種形式的單例最為簡單且直接。那就是用final關(guān)鍵字全局聲明的時(shí)候直接實(shí)例化添怔,然后將構(gòu)造函數(shù)進(jìn)行私有化就可以完成一個(gè)單例了湾戳。

    private static final UserInfo instance = new UserInfo();

    public static UserInfo getInstance() {
        return instance;
    }

    private UserInfo() {
    }

說明

這種實(shí)現(xiàn)非常簡單且直接,同時(shí)帶來的就是資源的消耗广料,無論需不需要用的到這個(gè)對(duì)象都會(huì)被實(shí)例化砾脑。

懶漢式

相對(duì)于餓漢式來說,懶漢式只有在首次需要用到單例的時(shí)候才會(huì)被實(shí)例化艾杏,如果不需要那么就不會(huì)被實(shí)例化韧衣。

    private static UserInfo instance = null;

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

    private UserInfo() {
    }

當(dāng)getInstance被調(diào)用的時(shí)候首先對(duì)instance對(duì)象進(jìn)行判空,隨后實(shí)例化返回instance對(duì)象购桑。

所謂懶漢和餓漢是一個(gè)形象說法畅铭,第一種由于聲明直接實(shí)例化,就像一個(gè)餓了的大漢一樣看到吃的就直接去吃勃蜘;懶漢呢就是需要時(shí)候再去獲取吃的硕噩。

說明

懶漢式相較于前面的餓漢式節(jié)省了內(nèi)存的開銷,當(dāng)需要單例對(duì)象的時(shí)候才會(huì)實(shí)例化單例對(duì)象元旬。但是對(duì)于多線程并發(fā)調(diào)用getInstance的時(shí)候榴徐,大家就會(huì)發(fā)現(xiàn)其實(shí)這時(shí)候并不能保證getInstance中只實(shí)例化了一個(gè)instance對(duì)象,后面的很多中實(shí)現(xiàn)其實(shí)也就是為了解決這個(gè)問題個(gè)出現(xiàn)的匀归。

線程安全

為了解決并發(fā)調(diào)用現(xiàn)象坑资,我們可以使用synchronized關(guān)鍵字,例如:

    private static  UserInfo instance = null;

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

    private UserInfo() {
    }

那么這個(gè)實(shí)現(xiàn)可以防止并發(fā)請(qǐng)求的時(shí)候進(jìn)行了多次實(shí)例化穆端。但是這樣就會(huì)導(dǎo)致多個(gè)線程被同時(shí)阻擋在getInstance方法袱贮,影響線程執(zhí)行性能。

為了提高性能体啰,減少被擋線程的可能性攒巍,我們對(duì)上面的實(shí)現(xiàn)進(jìn)行了修改

    private static  UserInfo instance = null;

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

    private UserInfo() {
    }

那么這樣就會(huì)不至于所有調(diào)用getInstance方法的線程都會(huì)被阻塞嗽仪,而且在一定程度上完成了上面的需求。但是這樣我們就會(huì)發(fā)現(xiàn)依然無法完全避免單例對(duì)象被重復(fù)實(shí)例化柒莉,原因在于首次調(diào)用getInstance發(fā)放被synchronized阻擋的線程數(shù)超過1的時(shí)候闻坚,就會(huì)導(dǎo)致額外的實(shí)例化操作。

雙重檢查加鎖

首先介紹一個(gè)關(guān)鍵字——volatile兢孝,這個(gè)關(guān)鍵字應(yīng)該不算陌生窿凤,我發(fā)現(xiàn)很多單例中都或多或少見過。
volatile關(guān)鍵字標(biāo)記的變量意味著線程共享內(nèi)存跨蟹!也就是在多線程程序中其實(shí)調(diào)用該對(duì)象是同一個(gè)內(nèi)存位置雳殊,以保證各個(gè)線程調(diào)用獲取到的是實(shí)時(shí)數(shù)據(jù)
這樣結(jié)合線程鎖配合使用,不但可以保證線程安全窗轩,而且可以保證實(shí)例的唯一性
注意:volatile可能屏蔽掉虛擬機(jī)優(yōu)化內(nèi)容夯秃,從而可能降低虛擬機(jī)運(yùn)行效率

范例

結(jié)合上面的線程并發(fā)方案,我們的實(shí)現(xiàn)可以修改為:

    private static volatile  UserInfo instance = null;

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

    private UserInfo() {
    }

其實(shí)和上面的最后實(shí)現(xiàn)沒什么不一樣痢艺,唯一的不同是多了一個(gè)關(guān)鍵字volatile的修飾仓洼。這個(gè)volatile修飾可以保證當(dāng)我們的getInstance中synchronized被擋住線程超過1的時(shí)候,線程1執(zhí)行實(shí)例化之后線程2繼續(xù)接著向下執(zhí)行腹备,這時(shí)候線程2就會(huì)獲取instance對(duì)象真實(shí)情況衬潦,然后進(jìn)行下面的程序執(zhí)行。
這種單例實(shí)現(xiàn)是目前看到比較多的植酥,因?yàn)闆]什么難度镀岛,而且比較實(shí)用。但是不太建議一個(gè)系統(tǒng)中過多使用友驮,如果項(xiàng)目中單例過多漂羊,那么也會(huì)導(dǎo)致性能缺陷,上面也提到了volatile關(guān)鍵字會(huì)屏蔽一些虛擬機(jī)的優(yōu)化方案

Lazy initialization holder class模式

這個(gè)模式綜合使用了Java的類級(jí)內(nèi)部類和多線程缺省同步鎖的知識(shí)卸留,很巧妙地同時(shí)實(shí)現(xiàn)了延遲加載和線程安全

介紹

Lazy Initialization Holder Class 模式主要是利用了java虛擬機(jī)類裝載機(jī)制走越,保證了getInstance方法調(diào)用前不會(huì)被裝載,第一次被調(diào)用之后內(nèi)部類才會(huì)被裝載耻瑟,而這時(shí)候同時(shí)會(huì)裝載類的靜態(tài)域

利用了類級(jí)內(nèi)部靜態(tài)類的加載旨指,從而延遲了內(nèi)部靜態(tài)類的裝載,即延遲了instance對(duì)象的實(shí)例化過程喳整!這是一個(gè)餓漢式的類級(jí)內(nèi)部類實(shí)現(xiàn)方式

因?yàn)閖ava裝載靜態(tài)內(nèi)容只會(huì)初始化一次谆构,因此保證了線程安全,其次也做到了延遲加載的目的

使用場景

這種實(shí)現(xiàn)方式更多的強(qiáng)調(diào)一種使用場景的可能性框都,而非單例類獨(dú)立為整個(gè)系統(tǒng)提供服務(wù)搬素。例如:一個(gè)系統(tǒng)內(nèi)部某個(gè)模塊內(nèi)部的配置項(xiàng),在這個(gè)過程中可以使用這種方式,當(dāng)該模塊未啟用的時(shí)候熬尺,其實(shí)沒有任何額外的資源開銷摸屠,但是一旦模塊被調(diào)用的時(shí)候,java虛擬機(jī)就會(huì)加載相關(guān)類粱哼,這時(shí)候連帶著靜態(tài)內(nèi)部類也被加載(有且只加載一次季二,而且這個(gè)由java虛擬機(jī)控制),這時(shí)候利用靜態(tài)內(nèi)部加載的配置內(nèi)容進(jìn)行模塊初始化等操作皂吮。

這種方式在公共庫戒傻、SDK開發(fā)中比較喜歡用到,尤其是對(duì)于平臺(tái)類的SDK開發(fā)蜂筹。原因就是一個(gè)SDK中可能存在多個(gè)模塊,而且這些模塊在沒有被初始化的時(shí)候希望不要打擾集成APP的業(yè)務(wù)和實(shí)現(xiàn)芦倒,乃至SDK某部分模塊的使用艺挪。

枚舉

使用枚舉來實(shí)現(xiàn)單實(shí)例控制會(huì)更加簡潔,而且無償?shù)靥峁┝诵蛄谢瘷C(jī)制兵扬,并由JVM從根本上提供保障麻裳,絕對(duì)防止多次實(shí)例化,是更簡潔器钟、高效津坑、安全的實(shí)現(xiàn)單例的方式

枚舉屬于引用數(shù)據(jù)類型還是基本數(shù)據(jù)類型?這個(gè)問題在java中相比很多人都說不清楚傲霸,其實(shí)我也講不明白疆瑰,但是我記得C#語言中屬于基本數(shù)據(jù)類型。
即使不是基本數(shù)據(jù)類型那么他也不會(huì)是普通引用數(shù)據(jù)類型昙啄,因?yàn)槊杜e的值是固定的穆役,而且不能夠被手動(dòng)實(shí)例化創(chuàng)建。也正是因?yàn)檫@個(gè)所以也成為了一個(gè)天然的常量裝載機(jī)制梳凛。
當(dāng)然枚舉單例用的很少耿币,因?yàn)閷?duì)象之所以被廣泛應(yīng)用就在于其操作可以依據(jù)特定條件發(fā)生變化,例如全局配置項(xiàng)韧拒。如果用了枚舉操作起來不是很方便淹接,而且很多單例是需要外界數(shù)據(jù)交互的,例如:當(dāng)前登錄賬戶叛溢,當(dāng)前設(shè)置項(xiàng)等

總結(jié)

單例模式實(shí)現(xiàn)方式很多種塑悼,無論哪一種都存在一些缺點(diǎn),依據(jù)使用場景做出選擇雇初。

日常項(xiàng)目使用比較多的還是屬于雙重檢查加鎖拢肆,在模塊比較多的時(shí)候,需要通過外部初始化固定模塊的情況下我們可以采用模塊獨(dú)立的Lazy initialization holder class模式 這種方式相對(duì)而言比較可靠,當(dāng)然也可以使用雙重檢查加鎖郭怪,但是上面也提到了java虛擬機(jī)會(huì)屏蔽一些優(yōu)化內(nèi)容支示,可能會(huì)影響性能,因此對(duì)于單例比較多的時(shí)候避免頻繁使用雙重檢查加鎖鄙才!
當(dāng)然Lazy initialization holder class模式實(shí)現(xiàn)一般相較而言比較麻煩一些颂鸿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市攒庵,隨后出現(xiàn)的幾起案子嘴纺,更是在濱河造成了極大的恐慌,老刑警劉巖浓冒,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栽渴,死亡現(xiàn)場離奇詭異,居然都是意外死亡稳懒,警方通過查閱死者的電腦和手機(jī)闲擦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來场梆,“玉大人墅冷,你說我怎么就攤上這事』蛴停” “怎么了寞忿?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長顶岸。 經(jīng)常有香客問我腔彰,道長,這世上最難降的妖魔是什么蜕琴? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任萍桌,我火速辦了婚禮,結(jié)果婚禮上凌简,老公的妹妹穿的比我還像新娘上炎。我一直安慰自己,他們只是感情好雏搂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布藕施。 她就那樣靜靜地躺著,像睡著了一般凸郑。 火紅的嫁衣襯著肌膚如雪裳食。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天芙沥,我揣著相機(jī)與錄音诲祸,去河邊找鬼浊吏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛救氯,可吹牛的內(nèi)容都是我干的找田。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼着憨,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼墩衙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起甲抖,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤漆改,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后准谚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挫剑,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年氛魁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暮顺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秀存,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出羽氮,到底是詐尸還是另有隱情或链,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布档押,位于F島的核電站澳盐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏令宿。R本人自食惡果不足惜叼耙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粒没。 院中可真熱鬧筛婉,春花似錦、人聲如沸癞松。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽响蓉。三九已至硕勿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間枫甲,已是汗流浹背源武。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工扼褪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粱栖。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓话浇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親查排。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凳枝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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