單例模式

一、概述

??單例模式目的是維護(hù)系統(tǒng)中全局唯一的實例化對象离福,并對外提供全局訪問的方法溅呢。

二澡屡、示例

??單例模式分為餓漢式和懶漢式兩類,也叫快加載和懶加載藕届,二者的區(qū)別在于加載時機(jī)不同挪蹭。餓漢式指的是在程序初始化的時候就實例化唯一的對象,懶漢式指的是在程序執(zhí)行過程中休偶,真正需要這個實例對象的時候才去加載梁厉。下面我們分別看一下如何實現(xiàn):

1. 餓漢式
public class Singleton{
    private static final Singleton instance = new Singleton();    
    // 限制默認(rèn)構(gòu)造方法的訪問權(quán)限
    private Singleton(){}
    
    // 提供獲取唯一實例對象的方法
    public static Singleton getInstance(){
        return instance;
    }
}

??餓漢式很好理解,就是在類加載的時候就把唯一的全局對象進(jìn)行實例化,由于在JVM中词顾,每個類只會加載一次八秃,所以這個唯一實例是線程安全的,優(yōu)點不言而喻:簡單粗暴肉盹、不易出錯昔驱。

2. 懶漢式
public class Singleton{
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

??按照上面懶漢式寫法,Singleton類加載的時候上忍,只是聲明出instance骤肛,只有在調(diào)用getInstance()時才會真正地去實例化,并且在第一次執(zhí)行實例化后窍蓝,后續(xù)調(diào)用getInstance()都會直接返回唯一的實例對象腋颠。這種寫法在單線程中沒有任何問題,但是在多線程中吓笙,就肯定會出問題了淑玫,主要的問題有兩個:(1)第一個線程示例化后,其他線程不是及時可見面睛,即內(nèi)存可見性無法保證絮蒿;(2)多個線程可能同時判斷出instance為空,就會導(dǎo)致多個線程都執(zhí)行實例化代碼叁鉴。那我們可以在現(xiàn)有代碼的基礎(chǔ)上進(jìn)行改進(jìn):

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

??這樣的寫法就會防止多個線程同時執(zhí)行實例化的代碼土涝,我們在getInstance方法上加一個synchronized,通過鎖機(jī)制來保證線程安全亲茅。但是回铛,每次調(diào)用getInstance()獲取單例都要獲取當(dāng)前類的鎖,就會導(dǎo)致所有線程變成了串行執(zhí)行克锣,效率肯定會受到影響茵肃。我們可以繼續(xù)改善:

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

??現(xiàn)在我們可以保證只有第一次初始化示例對象的時候才會加鎖,后續(xù)使用該示例對象都不會再次加鎖袭祟。但是這種寫法又復(fù)現(xiàn)了多次實例化的問題验残,即多個線程同時判斷到instance == null,然后依次執(zhí)行了實例化代碼巾乳。那應(yīng)該如何避免呢您没?答案是雙重檢查機(jī)制:

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

??加鎖前后,我們都對instance進(jìn)行了狀態(tài)判斷胆绊,確保只有一個線程會去實例化唯一的對象氨鹏,這么設(shè)計應(yīng)該是完美了。
??但是压状,我們忽略了一個很重要的點仆抵,就是JVM和CPU會有可能會對代碼指令重排跟继,這個重排是為了提升指令的執(zhí)行效率腮敌,而重排的原則是保證重排之后的指令執(zhí)行結(jié)果在單線程環(huán)境下和重排之前保持結(jié)果一致失球。上面的instance = new Singleton();這行代碼嫂冻,代表著實例化一個對象的過程狱意,這個過程整體上分為三步:1. 分配內(nèi)存;2. 初始化對象万牺;3. 將instance引用指向這塊內(nèi)存區(qū)域漩怎。
??如果虛擬機(jī)對這三步進(jìn)行了重排序餐济,那可能會變成1 - 3 - 2的順序趣竣,這樣就可能會出現(xiàn)異常情況了:第一個線程成功獲取到鎖摇庙,并且執(zhí)行了new Singleton(),當(dāng)實例化對象執(zhí)行到1 - 3的時候遥缕,第二個線程也調(diào)用了getInstance()方法跟匆,那么會發(fā)生什么呢?第二個線程在第一次判斷時就會得到false通砍,接著直接return instance;但這時候其實這個對象還沒有被實例化,只是指定了內(nèi)存區(qū)域烤蜕,那第二個線程使用這個對象就會出現(xiàn)問題封孙。
??為了解決上面指令重排的問題,我們加入volatile關(guān)鍵字讽营,來禁止虛擬機(jī)和CPU對關(guān)于instance的指令進(jìn)行重排序:

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

??優(yōu)化到這里虎忌,我們的懶漢加載模式終于大功告成,既保證了線程安全橱鹏,又最大限度地保證了并行處理的速度膜蠢。缺點就是邏輯判斷太多,代碼錯綜復(fù)雜莉兰,容易出錯挑围。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市糖荒,隨后出現(xiàn)的幾起案子杉辙,更是在濱河造成了極大的恐慌,老刑警劉巖捶朵,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜘矢,死亡現(xiàn)場離奇詭異,居然都是意外死亡综看,警方通過查閱死者的電腦和手機(jī)品腹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來红碑,“玉大人舞吭,你說我怎么就攤上這事。” “怎么了镣典?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵兔毙,是天一觀的道長。 經(jīng)常有香客問我兄春,道長澎剥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任赶舆,我火速辦了婚禮哑姚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芜茵。我一直安慰自己叙量,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布九串。 她就那樣靜靜地躺著绞佩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猪钮。 梳的紋絲不亂的頭發(fā)上品山,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機(jī)與錄音烤低,去河邊找鬼肘交。 笑死,一個胖子當(dāng)著我的面吹牛扑馁,可吹牛的內(nèi)容都是我干的涯呻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼腻要,長吁一口氣:“原來是場噩夢啊……” “哼复罐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起闯第,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤市栗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咳短,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體填帽,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年咙好,在試婚紗的時候發(fā)現(xiàn)自己被綠了篡腌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡勾效,死狀恐怖嘹悼,靈堂內(nèi)的尸體忽然破棺而出叛甫,到底是詐尸還是另有隱情,我是刑警寧澤杨伙,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布其监,位于F島的核電站,受9級特大地震影響限匣,放射性物質(zhì)發(fā)生泄漏抖苦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一米死、第九天 我趴在偏房一處隱蔽的房頂上張望锌历。 院中可真熱鬧,春花似錦峦筒、人聲如沸究西。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卤材。三九已至,卻和暖如春峦失,著一層夾襖步出監(jiān)牢的瞬間商膊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工宠进, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藐翎。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓材蹬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吝镣。 傳聞我的和親對象是個殘疾皇子堤器,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,107評論 2 356