13.1 大佬問我: notify()是隨機喚醒線程么?

大佬問我: notify()是隨機喚醒線程么?

我的內(nèi)心戲: 這不是顯而易見么! 肯定是啊! jdk關(guān)于notify()注釋都寫的很清楚!
不過這么簡單的問題?

image

機智如我, 決定再次裝小小白, 回答: 不是!

大佬: 很好, 小伙子你真的讓我刮目相看了!!

:

image

大佬: 說說為什么?

: ………………

image

牢不可破的知識點被大佬一問, 瞬間感覺哪里有點問題!

于是, 咸魚君開啟了求證模式.

(大佬問我不懂的也就算了, 問這種“共識”的, 我一定舉出例子駁倒他!)

代碼求證

身為碼農(nóng), 我決定寫代碼先驗證下!

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class NotifyTest{

    //等待列表, 用來記錄等待的順序
    private static List<String> waitList = new LinkedList<>();
    //喚醒列表, 用來喚醒的順序
    private static List<String> notifyList = new LinkedList<>();

    private static Object lock = new Object();


    public static void main(String[] args) throws InterruptedException{

        //創(chuàng)建50個線程
        for(int i=0;i<50;i++){
            String threadName = Integer.toString(i);
            new Thread(() -> {
                synchronized (lock) {
                    String cthreadName = Thread.currentThread().getName();
                    System.out.println("線程 ["+cthreadName+"] 正在等待.");
                    waitList.add(cthreadName);
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("線程 ["+cthreadName+"] 被喚醒了.");
                    notifyList.add(cthreadName);
                }
            },threadName).start();
            
            TimeUnit.MILLISECONDS.sleep(50);
        }

        TimeUnit.SECONDS.sleep(1);

        for(int i=0;i<50;i++){
            synchronized (lock) {
                lock.notify();
                TimeUnit.MILLISECONDS.sleep(10);
            }
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println("wait順序:"+waitList.toString());
        System.out.println("喚醒順序:"+notifyList.toString());
    }
}

代碼很簡單, 創(chuàng)建了50個線程, 對其wait()和notify(), 同時使用waitList和notifyList來記錄各自的順序!

跑一下代碼


image.png

沒任何懸念, 結(jié)果不就是證明了notify()是隨機喚醒線程的么?!!

我信心爆棚, 喊著大佬來看(雖然沒啥炫耀的, 但是能圓下“指導(dǎo)”大佬的夢想!)

大佬看了下代碼, 然后看著我, 微微一笑

內(nèi)心一慌: 難道有問題?

只見大佬默默的拿起我的鼠標(biāo), 剪切,粘貼了一行代碼

for(int i=0;i<50;i++){
   synchronized (lock) {
       lock.notify();
       //大佬把這行代碼移出了synchronized{}
       //TimeUnit.MILLISECONDS.sleep(10);
    }
   TimeUnit.MILLISECONDS.sleep(10);
  }
TimeUnit.SECONDS.sleep(1);

大佬再次微微一笑: 你再跑跑看!

image

看到大佬的自信從容, 我越來越慌,

image

趕緊運行下

image.png

看到這不可置信的結(jié)果, 我徹底慌了

image.png

什么?!! 這到底這么回事? 改動了一行代碼, notify()居然有序了?!!!

看著結(jié)果, 我沉思, 連大佬走了都沒注意.

究竟哪里出了問題? 難道notify()真是有序喚醒的?

于是,有了接下來的文章!

有疑問的小伙伴不妨看下去!(大佬可以退散了)

代碼問題分析

我們先分析下求證的代碼.

只是移動了下sleep()語句, 結(jié)果居然天差地別?!

其實問題就出在sleep()上, 準(zhǔn)確的是sleep()在synchronized里面還是外面.

當(dāng)我們執(zhí)行notify之后,由于sleep在symchronized內(nèi)部, 因此沒有釋放鎖!
(其實這點 大佬問我: notify()會立刻釋放鎖么?提起過)

lock.wait后 被通知到的線程隔缀,就會進入waitSet隊列;

之后我們循環(huán)時

for(int i=0;i<50;i++){
   synchronized (lock) {
     lock.notify();
     TimeUnit.MILLISECONDS.sleep(10);
   }
  TimeUnit.SECONDS.sleep(1);
}

lock.notify();去喚醒等待線程, 我們假設(shè)喚醒了線程A;

但是因為后面還要執(zhí)行TimeUnit.MILLISECONDS.sleep(10)所以lock鎖并沒有被釋放!

當(dāng)TimeUnit.MILLISECONDS.sleep(10)執(zhí)行完畢后, lock被釋放,

此時被喚醒的線程A想獲取lock,

但是我們的for循環(huán)中synchronized (lock)也想繼續(xù)獲取lock,

于是兩者發(fā)生了鎖競爭.

由于synchronized實際上不是公平鎖,其鎖競爭的機制具有隨機性

這就導(dǎo)致了最終, 我們看到的結(jié)果好像是隨機的!

當(dāng)我們把TimeUnit.MILLISECONDS.sleep(10);移出synchronized同步塊后

for(int i=0;i<50;i++){
    synchronized (lock) {
       lock.notify();
     }
    TimeUnit.MILLISECONDS.sleep(10);
  }
TimeUnit.SECONDS.sleep(1);

lock鎖立即被釋放了,

并且緊跟的 TimeUnit.SECONDS.sleep(1)確保被喚醒的線程能夠獲得lock鎖立刻執(zhí)行,

所以, 我們看到的結(jié)果才是正確的!

理論求證

想通了代碼, 得到了“notify是順序喚醒”的結(jié)果后,

不禁疑惑,

既然“notify是順序喚醒”的, 那為什么廣為流傳的, 深入人心的確實“notify()是隨機喚醒線程”,

JDK開發(fā)大佬不可能犯這樣的錯吧?!

帶著這樣的疑惑, 咸魚君選擇了看JDK源碼來求證!

這里以常用的JDK1.8源碼為例

找到“notify()”源碼, 看到了這段源碼注釋

image.png

翻譯一下, 大致意思就是:

notify在源碼的注釋中說到notify選擇喚醒的線程是任意的例书,但是依賴于具體實現(xiàn)的jvm.

看完后, 咸魚君頓時茅塞頓開!

我們都知道, JVM有很多實現(xiàn), 比較流行的就是hotspot!

帶著質(zhì)疑, 我們不妨接下去看看jdk1.8, hotspot中對于notify()究竟是如何實現(xiàn)的

synchronized的wait和notify是位于ObjectMonitor.cpp中

image.png

notify過程調(diào)用的是DequeueWaiter方法:

image.png

這里實際上是將_WaitSet中的第一個元素進行出隊操作,
這也說明了notify是個順序操作, 具有公平性.

看完源碼, 我們不難得出結(jié)論,

原來hotspot對notofy()的實現(xiàn)并不是我們以為的隨機喚醒, 而是“先進先出”的順序喚醒!

此刻, 我對大佬欽佩不已!

image

同時明白了兩個道理:

1. 廣為流傳的知識不一定是正確的, 有條件一定多看源碼, 敢于質(zhì)疑求證

2. 一定要注意版本, 沒有知識是一成不變的!

歡迎關(guān)注我

技術(shù)公眾號 “CTO技術(shù)”

訂閱號.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末算灸,一起剝皮案震驚了整個濱河市蛤吓,隨后出現(xiàn)的幾起案子充活,更是在濱河造成了極大的恐慌硼砰,老刑警劉巖且蓬,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異题翰,居然都是意外死亡恶阴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門豹障,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冯事,“玉大人,你說我怎么就攤上這事血公£墙觯” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵累魔,是天一觀的道長岩饼。 經(jīng)常有香客問我,道長薛夜,這世上最難降的妖魔是什么籍茧? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮梯澜,結(jié)果婚禮上寞冯,老公的妹妹穿的比我還像新娘。我一直安慰自己晚伙,他們只是感情好吮龄,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咆疗,像睡著了一般漓帚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上午磁,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天尝抖,我揣著相機與錄音毡们,去河邊找鬼。 笑死昧辽,一個胖子當(dāng)著我的面吹牛衙熔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搅荞,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼红氯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了咕痛?” 一聲冷哼從身側(cè)響起痢甘,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茉贡,沒想到半個月后产阱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡块仆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了王暗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悔据。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖俗壹,靈堂內(nèi)的尸體忽然破棺而出科汗,到底是詐尸還是另有隱情,我是刑警寧澤绷雏,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布头滔,位于F島的核電站,受9級特大地震影響涎显,放射性物質(zhì)發(fā)生泄漏坤检。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一期吓、第九天 我趴在偏房一處隱蔽的房頂上張望早歇。 院中可真熱鬧,春花似錦讨勤、人聲如沸箭跳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谱姓。三九已至,卻和暖如春刨晴,著一層夾襖步出監(jiān)牢的瞬間屉来,已是汗流浹背路翻。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奶躯,地道東北人帚桩。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像嘹黔,于是被迫代替她去往敵國和親账嚎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353