線程虛假喚醒的Java演示

什么是線程虛假喚醒

在不同的語言,甚至不同的操作系統(tǒng)上瀑晒,條件鎖都會產(chǎn)生虛假喚醒現(xiàn)象。所有語言的條件鎖庫都推薦用戶把wait()放進(jìn)循環(huán)里,參見為什么條件鎖會產(chǎn)生虛假喚醒現(xiàn)象(spurious wakeup)

while (!cond) {
    lock.wait();
}

摘選wikipedia的解釋:

This means that when you wait on a condition variable, the wait may (occasionally) return when no thread specifically broadcast or signaled that condition variable. Spurious wakeups may sound strange, but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations. The race conditions that cause spurious wakeups should be considered rare.

簡單翻譯一下: 當(dāng)線程在某個條件變量下等待時,即使其他線程沒有broadcast or signaled 這個條件變量,該線程仍然可能被喚醒,在多核處理器系統(tǒng)下,使條件變量完全可以預(yù)測會降低系統(tǒng)的性能,而導(dǎo)致虛假喚醒的幾率又很小

綜合我所了解到相關(guān)知識. 在操作系統(tǒng)底層"喚醒"的實現(xiàn)機制就注定虛假喚醒的存在,設(shè)計者們不解決這個問題的原因是

  1. 修復(fù)這個問題會導(dǎo)致系統(tǒng)性能下降,性價比太低
  2. 即使修復(fù)了這個問題,由于同步問題的存在,仍然要將wait()放進(jìn)循環(huán)里.

對于1不過多追究,對于2,下面以Java中的真實場景做演示

模擬場景

一個典型的生產(chǎn)者消費者場景,現(xiàn)有 2個consumer線程,一個producer線程,consumer和producer都是一次性的,它們都只會消費/生產(chǎn)一個產(chǎn)品,初始時產(chǎn)品數(shù)量為0,三個線程(近似)同時啟動

/**
 * @description: 參考了 http://www.reibang.com/p/da312eee4ac4
 * @author: alonwang
 * @create: 2019-07-19 15:54
 **/
public class SpuriousWakeUp {
    private final Object lock = new Object();
    private int product = 0;
        //如果沒有產(chǎn)品,在lock對象上等待喚醒,如果有產(chǎn)品,消費.
    private Runnable consumer = () -> {
        System.out.println(Thread.currentThread().getName() + " prepare consume");
        synchronized (lock) {
            if (product <= 0) {//替換為while解決線程虛假喚醒問題
                try {
                    System.out.println(Thread.currentThread().getName() + " wait");
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " wakeup");
            }
            product--;
            System.out.println(Thread.currentThread().getName() + " consumed product:" + product);
            if (product < 0) {
                System.err.println(Thread.currentThread().getName() + " spurious lock happend, product: " + product);
            }
        }
    };
        //生產(chǎn)一個產(chǎn)品然后喚醒一個在lock對象上等待的consumer
    private Runnable producer = () -> {
        System.out.println(Thread.currentThread().getName() + " prepare produce");
        synchronized (lock) {
            product += 1;
            System.out.println(Thread.currentThread().getName() + "produced product: " + product);
            lock.notify();
        }
    };

    public void producerAndConsumer() {
                // 啟動2個consumer,1個producer
        Thread c1 = new Thread(consumer);
        Thread c2 = new Thread(consumer);
        Thread p = new Thread(producer);
        c1.start();
        c2.start();
        p.start();

    }

    public static void main(String[] args) {
                //運行100次,以便觸發(fā)異炒仿耄現(xiàn)象
        for (int i = 0; i < 100; i++) {
            new SpuriousWakeUp().producerAndConsumer();
        }

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }
}

現(xiàn)象

預(yù)期現(xiàn)象: 不考慮三個線程的執(zhí)行順序,由于生產(chǎn)者只有一個,初始時又沒有產(chǎn)品.而consumer線程有兩個,那么一定會有一個consumer線程由于無法消費被永久阻塞.

實際現(xiàn)象: 某些情況下,應(yīng)該被永久阻塞的那個consumer線程被異常喚醒,并消費一個產(chǎn)品導(dǎo)致產(chǎn)品數(shù)量為-1.

截取的帶有異沉裕現(xiàn)象的輸出如下:

Thread-100 prepare consume
Thread-100 consumed product:0
Thread-99 wakeup
Thread-99 consumed product:-1
Thread-99 spurious lock happend, product: -1
Thread-108 prepare consume
Thread-108 wait
Thread-112 prepare consume

原理解釋

設(shè)兩個consumer為C1,C2,producer為P.上面出現(xiàn)異常的時序如下:

image.png

要理解問題所在,需要了解以下知識

  1. 線程獲取不到鎖被阻塞,會在Contention List上等待
  2. 獲取到鎖的線程調(diào)用wait后,會主動放棄鎖,并在Wait Set中等待喚醒
  3. 線程調(diào)用notify后,在退出Synchronized塊釋放鎖后才會執(zhí)行喚醒操作(暫時沒有搞清楚喚醒和釋放鎖的順序)

具體請參考Java鎖Synchronized之阻塞線程

上面問題的核心是: C1被喚醒后,仍然需要先獲取鎖再繼續(xù)執(zhí)行邏輯,而喚醒-獲取鎖并不是原子性的,喚醒之后鎖可能被其他線程獲取,這時C1再次獲取到鎖時,產(chǎn)品已經(jīng)沒了,由于是繼續(xù)執(zhí)行,就沒有再檢查產(chǎn)品數(shù)量,導(dǎo)致異常情況的出現(xiàn)

解決辦法—將if替換為while

替換為while后,即使被喚醒,仍然會再檢查一遍限制條件,保證邏輯的正確性.


線程虛假喚醒

Java鎖Synchronized之阻塞線程

Wikipedia的解釋

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兜材,隨后出現(xiàn)的幾起案子尉剩,更是在濱河造成了極大的恐慌窖维,老刑警劉巖祈纯,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轧邪,死亡現(xiàn)場離奇詭異,居然都是意外死亡举庶,警方通過查閱死者的電腦和手機执隧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來户侥,“玉大人镀琉,你說我怎么就攤上這事√砘觯” “怎么了滚粟?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵寻仗,是天一觀的道長刃泌。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么耙替? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任亚侠,我火速辦了婚禮,結(jié)果婚禮上俗扇,老公的妹妹穿的比我還像新娘硝烂。我一直安慰自己,他們只是感情好铜幽,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布滞谢。 她就那樣靜靜地躺著,像睡著了一般除抛。 火紅的嫁衣襯著肌膚如雪狮杨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天到忽,我揣著相機與錄音橄教,去河邊找鬼。 笑死喘漏,一個胖子當(dāng)著我的面吹牛护蝶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翩迈,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼持灰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了负饲?” 一聲冷哼從身側(cè)響起搅方,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绽族,沒想到半個月后姨涡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡吧慢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年涛漂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片检诗。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡匈仗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逢慌,到底是詐尸還是另有隱情悠轩,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布攻泼,位于F島的核電站火架,受9級特大地震影響鉴象,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜何鸡,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一纺弊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧骡男,春花似錦淆游、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吮炕,卻和暖如春已亥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背来屠。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工虑椎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俱笛。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓捆姜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迎膜。 傳聞我的和親對象是個殘疾皇子泥技,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

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