溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JVM源碼分析之Object.wait/notify實(shí)現(xiàn)

發(fā)布時間:2020-08-09 22:58:11 來源:網(wǎng)絡(luò) 閱讀:273 作者:java架構(gòu)師1 欄目:編程語言

最簡單的東西,往往包含了最復(fù)雜的實(shí)現(xiàn),因?yàn)樾枰獮樯蠈拥拇嬖谔峁┮粋€穩(wěn)定的基礎(chǔ),Object作為java中所有對象的基類,其存在的價值不言而喻,其中wait和notify方法的實(shí)現(xiàn)多線程協(xié)作提供了保證。

public class WaitNotifyCase {
    public static void main(String[] args) {
        final Object lock = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread A is waiting to get lock");
                synchronized (lock) {
                    try {
                        System.out.println("thread A get lock");
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println("thread A do wait method");
                        lock.wait();
                        System.out.println("wait end");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread B is waiting to get lock");
                synchronized (lock) {
                    System.out.println("thread B get lock");
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                    System.out.println("thread B do notify method");
                }
            }
        }).start();
    }
}

執(zhí)行結(jié)果:

thread A is waiting to get lock
thread A get lock
thread B is waiting to get lock
thread A do wait method
thread B get lock
thread B do notify method
wait end

前提:由同一個lock對象調(diào)用wait、notify方法。
1、當(dāng)線程A執(zhí)行wait方法時,該線程會被掛起;
2、當(dāng)線程B執(zhí)行notify方法時,會喚醒一個被掛起的線程A;
lock對象、線程A和線程B三者是一種什么關(guān)系?根據(jù)上面的結(jié)論,可以想象一個場景:
1、lock對象維護(hù)了一個等待隊(duì)列l(wèi)ist;
2、線程A中執(zhí)行l(wèi)ock的wait方法,把線程A保存到list中;
3、線程B中執(zhí)行l(wèi)ock的notify方法,從等待隊(duì)列中取出線程A繼續(xù)執(zhí)行;
當(dāng)然了,Hotspot實(shí)現(xiàn)不可能這么簡單。
上述代碼中,存在多個疑問:
1、進(jìn)入wait/notify方法之前,為什么要獲取synchronized鎖?
2、線程A獲取了synchronized鎖,執(zhí)行wait方法并掛起,線程B又如何再次獲取鎖?
為什么要使用synchronized?

static void Sort(int [] array) {
    // synchronize this operation so that some other thread can't
    // manipulate the array while we are sorting it. This assumes that other
    // threads also synchronize their accesses to the array.
    synchronized(array) {
        // now sort elements in array
    }
}

synchronized代碼塊通過javap生成的字節(jié)碼中包含 monitorenter monitorexit 指令。
JVM源碼分析之Object.wait/notify實(shí)現(xiàn)
執(zhí)行monitorenter指令可以獲取對象的monitor,而lock.wait()方法通過調(diào)用native方法wait(0)實(shí)現(xiàn),其中接口注釋中有這么一句:

The current thread must own this object's monitor.

表示線程執(zhí)行l(wèi)ock.wait()方法時,必須持有該lock對象的monitor,如果wait方法在synchronized代碼中執(zhí)行,該線程很顯然已經(jīng)持有了monitor。
代碼執(zhí)行過程分析
1、在多核環(huán)境下,線程A和B有可能同時執(zhí)行monitorenter指令,并獲取lock對象關(guān)聯(lián)的monitor,只有一個線程可以和monitor建立關(guān)聯(lián),假設(shè)線程A執(zhí)行加鎖成功;
2、線程B競爭加鎖失敗,進(jìn)入等待隊(duì)列進(jìn)行等待;
3、線程A繼續(xù)執(zhí)行,當(dāng)執(zhí)行到wait方法時,會發(fā)生什么?wait接口注釋:

This method causes the current thread to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object.

wait方法會將當(dāng)前線程放入wait set,等待被喚醒,并放棄lock對象上的所有同步聲明,意味著線程A釋放了鎖,線程B可以重新執(zhí)行加鎖操作,不過又有一個疑問:在線程A的wait方法釋放鎖,到線程B獲取鎖,這期間發(fā)生了什么?線程B是如何知道線程A已經(jīng)釋放了鎖?好迷茫....
4、線程B執(zhí)行加鎖操作成功,對于notify方法,JDK注釋:notify方法會選擇wait set中任意一個線程進(jìn)行喚醒;

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation

notifyAll方法的注釋:notifyAll方法會喚醒monitor的wait set中所有線程。

Wakes up all threads that are waiting on this object's monitor.

5、執(zhí)行完notify方法,并不會立馬喚醒等待線程,在notify方法后面加一段sleep代碼就可以看到效果,如果線程B執(zhí)行完notify方法之后sleep 5s,在這段時間內(nèi),線程B依舊持有monitor,線程A只能繼續(xù)等待;

那么wait set的線程什么時候會被喚醒?

想要解答這些疑問, 需要分析jvm的相關(guān)實(shí)現(xiàn),本文以HotSpot虛擬機(jī)1.7版本為例

什么是monitor?
在HotSpot虛擬機(jī)中,monitor采用ObjectMonitor實(shí)現(xiàn)。
JVM源碼分析之Object.wait/notify實(shí)現(xiàn)
每個線程都有兩個ObjectMonitor對象列表,分別為free和used列表,如果當(dāng)前free列表為空,線程將向全局global list請求分配ObjectMonitor。

ObjectMonitor對象中有兩個隊(duì)列:_WaitSet 和 _EntryList,用來保存ObjectWaiter對象列表;_owner指向獲得ObjectMonitor對象的線程。

JVM源碼分析之Object.wait/notify實(shí)現(xiàn)
_WaitSet :處于wait狀態(tài)的線程,會被加入到wait set;
_EntryList:處于等待鎖block狀態(tài)的線程,會被加入到entry set;
ObjectWaiter
JVM源碼分析之Object.wait/notify實(shí)現(xiàn)
ObjectWaiter對象是雙向鏈表結(jié)構(gòu),保存了_thread(當(dāng)前線程)以及當(dāng)前的狀態(tài)TState等數(shù)據(jù), 每個等待鎖的線程都會被封裝成ObjectWaiter對象。
wait方法實(shí)現(xiàn)
lock.wait()方法最終通過ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);實(shí)現(xiàn):
1、將當(dāng)前線程封裝成ObjectWaiter對象node;

JVM源碼分析之Object.wait/notify實(shí)現(xiàn)
2、通過ObjectMonitor::AddWaiter方法將node添加到_WaitSet列表中;
JVM源碼分析之Object.wait/notify實(shí)現(xiàn)
3、通過ObjectMonitor::exit方法釋放當(dāng)前的ObjectMonitor對象,這樣其它競爭線程就可以獲取該ObjectMonitor對象。
JVM源碼分析之Object.wait/notify實(shí)現(xiàn)
4、最終底層的park方法會掛起線程;
notify方法實(shí)現(xiàn)
lock.notify()方法最終通過ObjectMonitor的void notify(TRAPS)實(shí)現(xiàn):
1、如果當(dāng)前_WaitSet為空,即沒有正在等待的線程,則直接返回;
2、通過ObjectMonitor::DequeueWaiter方法,獲取_WaitSet列表中的第一個ObjectWaiter節(jié)點(diǎn),實(shí)現(xiàn)也很簡單。
這里需要注意的是,在jdk的notify方法注釋是隨機(jī)喚醒一個線程,其實(shí)是第一個ObjectWaiter節(jié)點(diǎn)
JVM源碼分析之Object.wait/notify實(shí)現(xiàn)

3、根據(jù)不同的策略,將取出來的ObjectWaiter節(jié)點(diǎn),加入到_EntryList或則通過Atomic::cmpxchg_ptr指令進(jìn)行自旋操作cxq,具體代碼實(shí)現(xiàn)有點(diǎn)長,這里就不貼了,有興趣的同學(xué)可以看objectMonitor::notify方法;

notifyAll方法實(shí)現(xiàn)

lock.notifyAll()方法最終通過ObjectMonitor的void notifyAll(TRAPS)實(shí)現(xiàn):

通過for循環(huán)取出_WaitSet的ObjectWaiter節(jié)點(diǎn),并根據(jù)不同策略,加入到_EntryList或則進(jìn)行自旋操作。

從JVM的方法實(shí)現(xiàn)中,可以發(fā)現(xiàn):notify和notifyAll并不會釋放所占有的ObjectMonitor對象,其實(shí)真正釋放ObjectMonitor對象的時間點(diǎn)是在執(zhí)行monitorexit指令,一旦釋放ObjectMonitor對象了,entry set中ObjectWaiter節(jié)點(diǎn)所保存的線程就可以開始競爭ObjectMonitor對象進(jìn)行加鎖操作了。

覺得不錯請點(diǎn)贊支持,歡迎留言或進(jìn)我的個人群855801563領(lǐng)取【架構(gòu)資料專題目合集90期】、【BATJTMD大廠JAVA面試真題1000+】,本群專用于學(xué)習(xí)交流技術(shù)、分享面試機(jī)會,拒絕廣告,我也會在群內(nèi)不定期答題、探討。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI