通俗理解單例模式-懶漢式雙重校驗(yàn)鎖

簡單的單例模式:(懶漢式)

package com.zcp.juc.single;

/**
 * @author zcp
 * @description
 * @created by 2020-03-26 22:50
 */
public final class Singleton {

    private static Singleton INSTANCE=null;

    private Singleton(){

    }

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

}

通俗講:單例=單個(gè)實(shí)例,即每次獲取相同的對象實(shí)例撵儿,因?yàn)閖ava中創(chuàng)建對象需要消耗資源,單例模式正好解決了對象的頻繁創(chuàng)建狐血。那么懶漢式是什么呢统倒?懶唄,我就是不想那么早初始化氛雪,有需要我再初始化房匆。

很多人都以為懶漢式寫到這,可是在多線程環(huán)境下呢报亩?上述代碼完了嗎浴鸿?
沒有!O易贰岳链!
問題來了:
有兩個(gè)線程T1、T2劲件,當(dāng)線程T1執(zhí)行到if條件判斷時(shí)掸哑,發(fā)現(xiàn)INSTANCE==null,還沒創(chuàng)建實(shí)例呢,T2線程也走到了這個(gè)if條件判斷零远,發(fā)現(xiàn)INSTANCE==null苗分,那么兩條線程繼續(xù)向下執(zhí)行,就會(huì)導(dǎo)致new了兩個(gè)對象牵辣,這顯然不符合單例模式摔癣,不是我們想要的結(jié)果。

怎么辦的纬向?
在有可能發(fā)生問題的地方加鎖择浊,不知道在哪?沒關(guān)系逾条,把整個(gè)方法都加上鎖

package com.zcp.juc.single;

/**
 * @author zcp
 * @description
 * @created by 2020-03-26 22:50
 */
public final class Singleton {

    private static Singleton INSTANCE=null;

    private Singleton(){

    }

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

}

在靜態(tài)方法上加鎖琢岩,相當(dāng)于對類對象加鎖

上述代碼等價(jià)于

package com.zcp.juc.single;

/**
 * @author zcp
 * @description
 * @created by 2020-03-26 22:50
 */
public final class Singleton {

    private static Singleton INSTANCE=null;

    private Singleton(){

    }

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

線程安全問題解決了,還有什么問題呢师脂?
問題來了:T1線程先拿到鎖担孔,T2線程阻塞,T1線程同步代碼塊執(zhí)行完畢危彩,成功創(chuàng)建了對象攒磨,釋放了鎖,此時(shí)T2線程拿到鎖汤徽,再執(zhí)行if判斷娩缰,發(fā)現(xiàn)實(shí)例已經(jīng)被初始化,大家不覺得很麻煩嗎谒府?為什么不直接告訴T2對象已經(jīng)被創(chuàng)建了拼坎,直接獲取就是了浮毯,還要加一次鎖,大哥啊泰鸡,加鎖不要錢罢丁?
怎么辦呢盛龄?
那么傳說中雙重判斷來了,在加鎖前判斷一次INSTANCE是否等于null饰迹,不等于直接就返回實(shí)例了,這樣也不用再加鎖判斷了余舶。(也就是首次訪問需要同步啊鸭,而之后就沒有synchronized了),這樣做還有問題嗎匿值?
biao急霸啤?
go on..

package com.zcp.juc.single;

/**
 * @author zcp
 * @description
 * @created by 2020-03-26 22:50
 */
public final 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;
    }
}

什么問題呢挟憔?
看字節(jié)碼(重點(diǎn)看17~24行字節(jié)碼指令)

 0: getstatic     #2                  // 獲取靜態(tài)變量INSTANCE
       3: ifnonnull     37           //判斷INSTANCE是不是Null   如果不是Null就跳轉(zhuǎn)執(zhí)行37行
       6: ldc           #3                  // 獲得了類對象 
       8: dup                              //把類對象的引用指針復(fù)制了一份
       9: astore_0                      //然后臨時(shí)存儲了復(fù)制的一份钟些,是為了將來解鎖用
      10: monitorenter              //開始執(zhí)行同步代碼塊
      11: getstatic     #2                  // 拿到靜態(tài)變量
      14: ifnonnull     27                  //如果不為Null,執(zhí)行27行
      17: new           #3                  // 為Null绊谭,繼續(xù)執(zhí)行政恍,創(chuàng)建對象,將對象的引用入棧
      20: dup                                // 復(fù)制一份這個(gè)對象的引用(引用地址)
      21: invokespecial #4                  // 利用對象的引用來調(diào)用構(gòu)造方法(根據(jù)引用地址調(diào)用)
      24: putstatic     #2                  // 原來的這一份的引用對應(yīng)賦值操作龙誊,把他賦值給靜態(tài)變量
      27: aload_0                        //把臨時(shí)存儲的類對象取出來
      28: monitorexit                    //解鎖抚垃,退出同步代碼塊
      29: goto          37                //跳轉(zhuǎn)到37行
      32: astore_1
      33: aload_0
      34: monitorexit
      35: aload_1
      36: athrow
      37: getstatic     #2                  // 獲取靜態(tài)變量
      40: areturn                            //返回結(jié)果
    Exception table:
       from    to  target type
          11    29    32   any
          32    35    32   any

_
17: new #3 // 創(chuàng)建對象,將對象的引用入棧 new Singleton()
20: dup // 復(fù)制一份這個(gè)對象的引用(引用地址)
21: invokespecial #4 // 利用對象的引用來調(diào)用構(gòu)造方法(根據(jù)引用地址調(diào)用)
24: putstatic #2 // 利用一份對象引用賦值給static Instance

jvm虛擬機(jī)在執(zhí)行時(shí)有可能做優(yōu)化(指令重排序優(yōu)化)趟大,也就是可能先執(zhí)行24,再執(zhí)行21铣焊,那么會(huì)導(dǎo)致什么問題呢逊朽?synchronized只可能保證同步代碼塊內(nèi)原子性(注:synchronized代碼塊內(nèi)的代碼仍可能發(fā)生有序性問題,即指令重排序)曲伊,但是無法保證外面if判斷叽讳。啥意思呢?

當(dāng)T1線程執(zhí)行到同步代碼塊內(nèi)坟募,發(fā)生了指令重排序岛蚤,先調(diào)用了24行的指令,將對象的引用賦值給了static Instance懈糯,那么此時(shí)T2執(zhí)行到同步代碼塊外面的if判斷涤妒,就會(huì)發(fā)現(xiàn)Instance不為Null,就繼續(xù)執(zhí)行返回,可返回的時(shí)候赚哗,T1還未將構(gòu)造方法初始完畢她紫。

總結(jié):

  • 關(guān)鍵在于0:getstatic在monitor外面硅堆,就好像不守規(guī)矩的人,他可以越過monitor讀取Instance變量的值
  • T1還未完全將構(gòu)造方法初始完畢贿讹,如果構(gòu)造方法內(nèi)要執(zhí)行很多初始化操作渐逃,那么T2拿走的將是一個(gè)未初始化完畢的實(shí)例
  • 對Instance使用volatile修飾即可,可以禁止指令重排序民褂。

最終完全的代碼:

package com.zcp.juc.single;

/**
 * @author zcp
 * @description
 * @created by 2020-03-26 22:50
 */
public final 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;
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茄菊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子赊堪,更是在濱河造成了極大的恐慌面殖,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雹食,死亡現(xiàn)場離奇詭異畜普,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)群叶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門吃挑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人街立,你說我怎么就攤上這事舶衬。” “怎么了赎离?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵逛犹,是天一觀的道長。 經(jīng)常有香客問我梁剔,道長虽画,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任荣病,我火速辦了婚禮码撰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘个盆。我一直安慰自己脖岛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布颊亮。 她就那樣靜靜地躺著柴梆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪终惑。 梳的紋絲不亂的頭發(fā)上绍在,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼揣苏。 笑死悯嗓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的卸察。 我是一名探鬼主播脯厨,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坑质!你這毒婦竟也來了合武?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤涡扼,失蹤者是張志新(化名)和其女友劉穎稼跳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吃沪,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汤善,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了票彪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片红淡。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖降铸,靈堂內(nèi)的尸體忽然破棺而出在旱,到底是詐尸還是另有隱情,我是刑警寧澤推掸,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布桶蝎,位于F島的核電站,受9級特大地震影響谅畅,放射性物質(zhì)發(fā)生泄漏登渣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一毡泻、第九天 我趴在偏房一處隱蔽的房頂上張望绍豁。 院中可真熱鬧,春花似錦牙捉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至无拗,卻和暖如春带到,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背英染。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工揽惹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婴梧,地道東北人贮预。 一個(gè)月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親这刷。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

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

  • C++和雙重檢查鎖定模式(DCLP)的風(fēng)險(xiǎn) 多線程其實(shí)就是指兩個(gè)任務(wù)一前一后或者同時(shí)發(fā)生弹澎。 1 簡介 當(dāng)你在網(wǎng)上搜...
    鮑陳飛閱讀 889評論 0 1
  • 一個(gè)簡單的單例示例 單例模式可能是大家經(jīng)常接觸和使用的一個(gè)設(shè)計(jì)模式绞铃,你可能會(huì)這么寫 publicclassUnsa...
    Martin說閱讀 2,213評論 0 6
  • java的設(shè)計(jì)模式,相信大家在學(xué)習(xí)java的時(shí)候經(jīng)常聽說囱嫩,可是什么是單例模式呢恃疯?它又是如何實(shí)現(xiàn)的呢?我將在下面的文...
    AubreyXue閱讀 221評論 1 0
  • 我知道 時(shí)光是可以慢下來的 腳步 也是可以停留下來的 而我也愿意把時(shí)間 荒廢在你的世界 沉睡在你的柔波里 嘀嗒墨闲,嘀...
    賴小賢閱讀 276評論 4 3
  • 有一次今妄,印度前總理甘地夫人的大兒子拉吉夫要做手術(shù)。醫(yī)生打算說一些“善意的謊言”安慰孩子鸳碧,但甘地夫人阻止了醫(yī)生...
    蓋金輝教育碎思閱讀 1,129評論 0 6