Java設(shè)計模式系列-單例模式


原創(chuàng)文章,轉(zhuǎn)載請標(biāo)注出處:《Java設(shè)計模式系列-單例模式》


一纷纫、概述

????????所謂單例枕扫,指的就是單實例,有且僅有一個類實例辱魁,這個單例不應(yīng)該由人來控制烟瞧,而應(yīng)該由代碼來限制,強(qiáng)制單例染簇。

????????單例有其獨有的使用場景参滴,一般是對于那些業(yè)務(wù)邏輯上限定不能多例只能單例的情況,例如:類似于計數(shù)器之類的存在剖笙,一般都需要使用一個實例來進(jìn)行記錄卵洗,若多例計數(shù)則會不準(zhǔn)確。

????????其實單例就是那些很明顯的使用場合弥咪,沒有之前學(xué)習(xí)的那些模式所使用的復(fù)雜場景过蹂,只要你需要使用單例,那你就使用單例聚至,簡單易理解酷勺。

????????所以我認(rèn)為有關(guān)單例模式的重點不在于場景,而在于如何使用扳躬。

二脆诉、單例實現(xiàn)

2.1 懶漢式

????????何為懶?顧名思義贷币,就是不做事击胜,這里也是同義,懶漢式就是不在系統(tǒng)加載時就創(chuàng)建類的單例役纹,而是在第一次使用實例的時候再創(chuàng)建偶摔。

????????詳見下方代碼示例:

public class LHanDanli {
    // 定義一個私有類變量來存放單例,私有的目的是指外部無法
    // 直接獲取這個變量促脉,而要使用提供的公共方法來獲取
    private static LHanDanli dl = null;
    // 定義私有構(gòu)造器辰斋,表示只在類內(nèi)部使用,
    // 亦指單例的實例只能在單例類內(nèi)部創(chuàng)建
    private LHanDanli(){}
    // 定義一個公共的公開的方法來返回該類的實例瘸味,
    // 由于是懶漢式宫仗,需要在第一次使用時生成實例,
    // 所以為了線程安全旁仿,使用synchronized關(guān)鍵字來確保只會生成單例
    public static synchronized LHanDanli getInstance(){
        if(dl == null){
            dl = new LHanDanli();
        }
        return dl;
    }
}

2.2 餓漢式

????????又何為餓藕夫?餓者,饑不擇食;但凡有食汁胆,必急食之梭姓。此處同義:在加載類的時候就會創(chuàng)建類的單例,并保存在類中嫩码。

????????詳見下方代碼示例:

public class EHanDanli {
    // 此處定義類變量實例并直接實例化,
    // 在類加載的時候就完成了實例化并保存在類中
    private static EHanDanli dl = new EHanDanli();
    // 定義無參構(gòu)造器罪既,用于單例實例
    private EHanDanli(){}
    // 定義公開方法铸题,返回已創(chuàng)建的單例
    public static EHanDanli getInstance(){
        return dl;
    }
}

2.3 雙重加鎖機(jī)制

????????何為雙重加鎖機(jī)制?

????????在懶漢式實現(xiàn)單例模式的代碼中琢感,有使用synchronized關(guān)鍵字來同步獲取實例丢间,保證單例的唯一性,但是上面的代碼在每一次執(zhí)行時都要進(jìn)行同步和判斷驹针,無疑會拖慢速度烘挫,使用雙重加鎖機(jī)制正好可以解決這個問題:

public class SLHanDanli {
    private static volatile SLHanDanli dl = null;
    private SLHanDanli(){}
    public static SLHanDanli getInstance(){
        if(dl == null){
            synchronized (SLHanDanli.class) {
                if(dl == null){
                    dl = new SLHanDanli();
                }
            }
        }
        return dl;
    }
}

????????看了上面的代碼,有沒有感覺很無語柬甥,雙重加鎖難道不是需要兩個synchronized進(jìn)行加鎖的嗎饮六?

????????...

????????其實不然,這里的雙重指的的雙重判斷苛蒲,而加鎖單指那個synchronized卤橄,為什么要進(jìn)行雙重判斷,其實很簡單臂外,第一重判斷窟扑,如果單例已經(jīng)存在,那么就不再需要進(jìn)行同步操作漏健,而是直接返回這個實例嚎货,如果沒有創(chuàng)建,才會進(jìn)入同步塊蔫浆,同步塊的目的與之前相同殖属,目的是為了防止有兩個調(diào)用同時進(jìn)行時,導(dǎo)致生成多個實例克懊,有了同步塊忱辅,每次只能有一個線程調(diào)用能訪問同步塊內(nèi)容,當(dāng)?shù)谝粋€搶到鎖的調(diào)用獲取了實例之后谭溉,這個實例就會被創(chuàng)建墙懂,之后的所有調(diào)用都不會進(jìn)入同步塊,直接在第一重判斷就返回了單例扮念。至于第二個判斷损搬,個人感覺有點查遺補(bǔ)漏的意味在內(nèi)(期待高人高見)。

????????補(bǔ)充:關(guān)于鎖內(nèi)部的第二重空判斷的作用,當(dāng)多個線程一起到達(dá)鎖位置時巧勤,進(jìn)行鎖競爭嵌灰,其中一個線程獲取鎖,如果是第一次進(jìn)入則dl為null颅悉,會進(jìn)行單例對象的創(chuàng)建沽瞭,完成后釋放鎖,其他線程獲取鎖后就會被空判斷攔截剩瓶,直接返回已創(chuàng)建的單例對象驹溃。

????????不論如何,使用了雙重加鎖機(jī)制后延曙,程序的執(zhí)行速度有了顯著提升豌鹤,不必每次都同步加鎖。

????????其實我最在意的是volatile的使用枝缔,volatile關(guān)鍵字的含義是:被其所修飾的變量的值不會被本地線程緩存布疙,所有對該變量的讀寫都是直接操作共享內(nèi)存來實現(xiàn),從而確保多個線程能正確的處理該變量愿卸。該關(guān)鍵字可能會屏蔽掉虛擬機(jī)中的一些代碼優(yōu)化灵临,所以其運行效率可能不是很高,所以擦酌,一般情況下俱诸,并不建議使用雙重加鎖機(jī)制,酌情使用才是正理赊舶!

????????更進(jìn)一步說睁搭,其實使用volatile的目的是為了防止暴露一個未初始化的不完整單例實例,導(dǎo)致系統(tǒng)崩潰笼平。因為創(chuàng)建單例實例其實需要經(jīng)過以下幾步:首先分配內(nèi)存空間园骆、然后將內(nèi)存空間的首地址指向引用(指針),最后調(diào)用構(gòu)造器創(chuàng)建實例寓调,由于在第二步的時候這個引用(指針)就會變的非null锌唾,那么在第三步未執(zhí)行,真正的單例實例還未創(chuàng)建完成的時候夺英,一個線程過來在第一個校驗中為false晌涕,將會直接將不完整的實例返回,從而造成系統(tǒng)崩潰痛悯。

2.4 類級內(nèi)部類方式

????????餓漢式會占用較多的空間余黎,因為其在類加載時就會完成實例化,而懶漢式又存在執(zhí)行速率慢的情況载萌,雙重加鎖機(jī)制呢惧财?又有執(zhí)行效率差的毛病巡扇,有沒有一種完美的方式可以規(guī)避這些毛病呢?

????????貌似有的垮衷,就是使用類級內(nèi)部類結(jié)合多線程默認(rèn)同步鎖厅翔,同時實現(xiàn)延遲加載和線程安全。

public class ClassInnerClassDanli {
    public static class DanliHolder{
        private static ClassInnerClassDanli dl = new ClassInnerClassDanli();
    }
    private ClassInnerClassDanli(){}
    public static ClassInnerClassDanli getInstance(){
        return DanliHolder.dl;
    }
}

????????如上代碼搀突,所謂類級內(nèi)部類刀闷,就是靜態(tài)內(nèi)部類,這種內(nèi)部類與其外部類之間并沒有從屬關(guān)系仰迁,加載外部類的時候涩赢,并不會同時加載其靜態(tài)內(nèi)部類,只有在發(fā)生調(diào)用的時候才會進(jìn)行加載轩勘,加載的時候就會創(chuàng)建單例實例并返回,有效實現(xiàn)了懶加載(延遲加載)怯邪,至于同步問題绊寻,我們采用和餓漢式同樣的靜態(tài)初始化器的方式,借助JVM來實現(xiàn)線程安全悬秉。

????????其實使用靜態(tài)初始化器的方式會在類加載時創(chuàng)建類的實例澄步,但是我們將實例的創(chuàng)建顯式放置在靜態(tài)內(nèi)部類中,它會導(dǎo)致在外部類加載時不進(jìn)行實例創(chuàng)建和泌,這樣就能實現(xiàn)我們的雙重目的:延遲加載和線程安全村缸。

四、使用

????????在Spring中創(chuàng)建的Bean實例默認(rèn)都是單例模式存在的武氓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梯皿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子县恕,更是在濱河造成了極大的恐慌东羹,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忠烛,死亡現(xiàn)場離奇詭異属提,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)美尸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門冤议,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人师坎,你說我怎么就攤上這事恕酸。” “怎么了屹耐?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵尸疆,是天一觀的道長椿猎。 經(jīng)常有香客問我,道長寿弱,這世上最難降的妖魔是什么犯眠? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮症革,結(jié)果婚禮上筐咧,老公的妹妹穿的比我還像新娘。我一直安慰自己噪矛,他們只是感情好量蕊,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著艇挨,像睡著了一般残炮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缩滨,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天势就,我揣著相機(jī)與錄音,去河邊找鬼脉漏。 笑死苞冯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侧巨。 我是一名探鬼主播舅锄,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼司忱!你這毒婦竟也來了皇忿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤烘贴,失蹤者是張志新(化名)和其女友劉穎禁添,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桨踪,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡老翘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了锻离。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铺峭。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖汽纠,靈堂內(nèi)的尸體忽然破棺而出卫键,到底是詐尸還是另有隱情,我是刑警寧澤虱朵,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布莉炉,位于F島的核電站钓账,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏絮宁。R本人自食惡果不足惜梆暮,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绍昂。 院中可真熱鬧啦粹,春花似錦、人聲如沸窘游。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忍饰。三九已至贪嫂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間艾蓝,已是汗流浹背撩荣。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留饶深,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓逛拱,卻偏偏與公主長得像敌厘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子朽合,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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