并發(fā)線程-雙重檢查鎖定問題

雙重檢查鎖定問題:Double-checked Locking

1致讥、先來看問題代碼

有線程安全問題的代碼塊-雙重檢查鎖定問題

- 代碼

    // 單例模式構(gòu)建對(duì)象
    public static LocalCache getInstance(){
        // 雙重檢測(cè)鎖仅仆,提高運(yùn)行效率
        if (instance == null){
            synchronized (LocalCache.class){
                if (instance == null) {
                    instance = new LocalCache();
                }
            }
        }
        return instance;
    }

代碼(該方法)的預(yù)期目標(biāo)是使用 懶漢模式 的單例設(shè)計(jì)模式獲取對(duì)象,阿里插件顯示這段代碼是線程不安全的

2垢袱、改進(jìn)方案

2.1墓拜、懶漢模式改為餓漢模式

這種思路有幾種實(shí)現(xiàn)方式,這里貼一種请契,是靜態(tài)代碼塊實(shí)例化一次對(duì)象

懶漢模式的一種實(shí)現(xiàn)方式

- 代碼

    static {
        instance = new LocalCache();
    }

    // 單例模式構(gòu)建對(duì)象
    public static LocalCache getInstance(){
        return instance;
    }

2.2咳榜、同為懶漢模式下的代碼改進(jìn)

可行的方案
instance變量加上 volatile 關(guān)鍵字夏醉,保證變量值的可見性

    /**
     * volatile的可見性,可以確保拿到 instance 的最終值
     */
    private static volatile LocalCache instance;

    // 單例模式構(gòu)建對(duì)象
    public static LocalCache getInstance(){
        // 雙重檢測(cè)鎖涌韩,提高運(yùn)行效率
        if (instance == null){
            synchronized (LocalCache.class){
                if (instance == null) {
                    instance = new LocalCache();
                }
            }
        }
        return instance;
    }

否定的方案(這個(gè)方案也不能保證線程安全畔柔,這里有點(diǎn)不太懂)

  // 單例模式構(gòu)建對(duì)象
    public static LocalCache getInstance(){
        // 雙重檢測(cè)鎖,提高運(yùn)行效率
        if (instance == null){
            synchronized (LocalCache.class){
                if (instance == null) {
                    LocalCache localCache = new LocalCache();
                    instance = localCache;
                }
            }
        }
        return instance;
    }

3臣樱、雙重檢測(cè)鎖定問題產(chǎn)生的原因

3.1靶擦、簡(jiǎn)單來說

instance = new LocalCache();這行代碼有三步操作(《碼出高效Java開發(fā)手冊(cè)》P233頁(yè)提到有兩步操作,個(gè)人感覺不太好解釋):

  • 初始化 LocalCache 實(shí)例
  • 為本來為null的instance變量開辟內(nèi)存空間雇毫,并確定默認(rèn)大行丁(這一點(diǎn)《碼出高效》P233頁(yè)書中并沒有提到)
  • 將對(duì)象地址寫進(jìn) instance 字段
    這三步操作并不是原子化的

3.2、舉個(gè)例子

  • 線程A進(jìn)入到if(instance == null){的時(shí)候棚放,instance為null
  • 線程A進(jìn)入同步代碼塊(synchronized括起來的代碼塊)枚粘,到instance = new LocalCache();時(shí)執(zhí)行了 為instance開辟內(nèi)存空間將對(duì)象的引用存入內(nèi)存空間 的動(dòng)作,但是沒有實(shí)例化LocalCache對(duì)象
  • 線程B執(zhí)行到if(instance == null){的時(shí)候飘蚯,instance不為null(但是實(shí)際沒有指向某個(gè)堆內(nèi)的內(nèi)存馍迄,簡(jiǎn)而言之,這塊內(nèi)存空間(棧的內(nèi)存空間)的引用地址指向的(堆的)內(nèi)存空間中沒有實(shí)際對(duì)象)局骤,所以直接return了一個(gè)中間態(tài)(我自己起的名字攀圈。。)的instance
  • 線程B中庄涡,接下來的代碼邏輯中量承,拿到instance的值其實(shí)是有問題的(有啥問題?-TODO- 反正是有問題的-_-)

這篇blog有相關(guān)介紹
所以這里涉及到指令重排的問題(可能也有叫“指令優(yōu)化”的-《碼出高效》P232-P232有提到)穴店,即#3.1的三步操作撕捍,CPU在執(zhí)行的時(shí)候并不會(huì)根據(jù)代碼里理解的順序(從上到下、從左到右)執(zhí)行泣洞,會(huì)判斷怎樣的組合可以提高效率忧风,重新排列指令執(zhí)行的順序(如圖)

指令重排/指令優(yōu)化 導(dǎo)致的線程安全問題

3.3、使用了volatile之后

這里用到的是volatile的防止指令重排的能力(JDK1.5之后才有的)-- volatile還有一個(gè)可見性的能力球凰,這里貌似沒有體現(xiàn)(下篇文章探討volatile 可見性/指令重排 問題)

  • 線程A進(jìn)入到if(instance == null){的時(shí)候狮腿,instance為null
  • 線程A進(jìn)入同步代碼塊(synchronized括起來的代碼塊),到instance = new LocalCache();時(shí)執(zhí)行了 為instance開辟內(nèi)存空間實(shí)例化LocalCache對(duì)象 的動(dòng)作呕诉,但是沒有將對(duì)象的引用存入內(nèi)存空間
  • 線程B執(zhí)行到if(instance == null){的時(shí)候缘厢,instance為null
  • 接下去就是預(yù)期的執(zhí)行流程了

4、復(fù)現(xiàn)雙重檢測(cè)問題的方式-供參考

KΥ臁贴硫!實(shí)際復(fù)現(xiàn)過程中并沒有復(fù)現(xiàn)問題,嚴(yán)重懷疑是復(fù)現(xiàn)方式還可以改進(jìn),以下復(fù)現(xiàn)方式僅供參考

懶漢模式加載的單例對(duì)象類

public class LocalCache {

    private static LocalCache instance;

    // 構(gòu)造方法私有化英遭,防止實(shí)例化
    private LocalCache() {}

    // 單例模式構(gòu)建對(duì)象
    public static LocalCache getInstance() throws InterruptedException {
        // // 雙重檢測(cè)鎖间护,提高運(yùn)行效率
        if (instance == null){
            synchronized (LocalCache.class){
                if (instance == null) {
                    instance = new LocalCache();
                }
            }
        }
        return instance;
    }
}

建了兩個(gè)線程工廠,每個(gè)工廠里面有兩根線程(總共4根)挖诸,模擬多線程環(huán)境(有更簡(jiǎn)便的寫法)

public class TestJava {
    public static void main(String[] args) {
        BlockingQueue blockingDeque = new LinkedBlockingDeque(2);
        TestThreadFactory firstFactory = new TestThreadFactory("第一個(gè)線程池");
        TestThreadFactory secondFactory = new TestThreadFactory("第二個(gè)線程池");
        TestRejectHandler testRejectHandler = new TestRejectHandler();
        ThreadPoolExecutor firstThreadPool = new ThreadPoolExecutor(2, 2, Integer.MAX_VALUE, TimeUnit.SECONDS, blockingDeque, firstFactory, testRejectHandler);
        ThreadPoolExecutor secondThreadPool = new ThreadPoolExecutor(2, 2, Integer.MAX_VALUE, TimeUnit.SECONDS,
                blockingDeque, secondFactory, testRejectHandler);
        Task task = new Task();
        for (int i = 0; i < 2; i++){
            firstThreadPool.execute(task);
            secondThreadPool.execute(task);
        }
    }

    /**
     * 線程工廠
     */
    public static class TestThreadFactory implements ThreadFactory{

        private final String namePrefix;
        private final AtomicInteger nextId = new AtomicInteger(1);

        public TestThreadFactory(String namePrefix) {
            this.namePrefix = "TestThreadFactory's " + namePrefix + "-worker-";
        }

        @Override
        public Thread newThread(Runnable task) {
            String name = namePrefix + nextId.getAndIncrement();
            Thread thread = new Thread(null, task, name, 0);
            System.out.println(thread);
            return thread;
        }
    }

    /**
     * 實(shí)際執(zhí)行任務(wù)
     */
    public static class Task implements Runnable{

        private final AtomicLong count = new AtomicLong(0L);
        @Override
        public void run() {
            try {
                LocalCache instance = LocalCache.getInstance();
                // todo 這里做一些instance對(duì)象的操作
                System.out.println("running_" + count.getAndIncrement() + ", instance: " + instance);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 當(dāng)線程異常的時(shí)候汁尺,可以打印線程異常堆棧
     */
    public static class TestRejectHandler implements RejectedExecutionHandler{

        @Override
        public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) {
            System.out.println("task rejected. " + executor.toString());
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市多律,隨后出現(xiàn)的幾起案子痴突,更是在濱河造成了極大的恐慌,老刑警劉巖狼荞,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苞也,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡粘秆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門收毫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來攻走,“玉大人,你說我怎么就攤上這事此再∥袈В” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵输拇,是天一觀的道長(zhǎng)摘符。 經(jīng)常有香客問我,道長(zhǎng)策吠,這世上最難降的妖魔是什么逛裤? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮猴抹,結(jié)果婚禮上带族,老公的妹妹穿的比我還像新娘。我一直安慰自己蟀给,他們只是感情好蝙砌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著跋理,像睡著了一般择克。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上前普,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天肚邢,我揣著相機(jī)與錄音,去河邊找鬼汁政。 笑死道偷,一個(gè)胖子當(dāng)著我的面吹牛缀旁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勺鸦,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼并巍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了换途?” 一聲冷哼從身側(cè)響起懊渡,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎军拟,沒想到半個(gè)月后剃执,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懈息,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年肾档,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辫继。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怒见,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姑宽,到底是詐尸還是另有隱情遣耍,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布炮车,位于F島的核電站舵变,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瘦穆。R本人自食惡果不足惜纪隙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扛或。 院中可真熱鬧瘫拣,春花似錦、人聲如沸告喊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)黔姜。三九已至拢切,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秆吵,已是汗流浹背淮椰。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人主穗。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓泻拦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親忽媒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子争拐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355