JVM源碼分析之Object.wait

鏈接:https://www.jianshu.com/p/f4454164c017

最簡單的東西,往往包含了最複雜的實現,因為需要為上層的存在提供一個穩定的基礎,Object作為java中所有對象的基類,其存在的價值不言而喻,其中wait和notify方法的實現多線程協作提供了保證。

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();
}
}

執行結果:

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對象調用wait、notify方法。

1、當線程A執行wait方法時,該線程會被掛起;

2、當線程B執行notify方法時,會喚醒一個被掛起的線程A;

lock對象、線程A和線程B三者是一種什麼關係?根據上面的結論,可以想象一個場景:

1、lock對象維護了一個等待隊列list;

2、線程A中執行lock的wait方法,把線程A保存到list中;

3、線程B中執行lock的notify方法,從等待隊列中取出線程A繼續執行;

當然了,Hotspot實現不可能這麼簡單。

上述代碼中,存在多個疑問:

1、進入wait/notify方法之前,為什麼要獲取synchronized鎖?

2、線程A獲取了synchronized鎖,執行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生成的字節碼中包含 ** monitorenter ** 和 ** monitorexit **指令。

JVM源碼分析之Object.wait/notify實現

執行monitorenter指令可以獲取對象的monitor,而lock.wait()方法通過調用native方法wait(0)實現,其中接口註釋中有這麼一句:

The current thread must own this object's monitor.

表示線程執行lock.wait()方法時,必須持有該lock對象的monitor,如果wait方法在synchronized代碼中執行,該線程很顯然已經持有了monitor。

代碼執行過程分析

1、在多核環境下,線程A和B有可能同時執行monitorenter指令,並獲取lock對象關聯的monitor,只有一個線程可以和monitor建立關聯,假設線程A執行加鎖成功;

2、線程B競爭加鎖失敗,進入等待隊列進行等待;

3、線程A繼續執行,當執行到wait方法時,會發生什麼?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方法會將當前線程放入wait set,等待被喚醒,並放棄lock對象上的所有同步聲明,意味著線程A釋放了鎖,線程B可以重新執行加鎖操作,不過又有一個疑問:在線程A的wait方法釋放鎖,到線程B獲取鎖,這期間發生了什麼?線程B是如何知道線程A已經釋放了鎖?好迷茫....

4、線程B執行加鎖操作成功,對於notify方法,JDK註釋:notify方法會選擇wait set中任意一個線程進行喚醒;

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、執行完notify方法,並不會立馬喚醒等待線程,在notify方法後面加一段sleep代碼就可以看到效果,如果線程B執行完notify方法之後sleep 5s,在這段時間內,線程B依舊持有monitor,線程A只能繼續等待;

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

想要解答這些疑問, 需要分析jvm的相關實現,本文以HotSpot虛擬機1.7版本為例

什麼是monitor?

在HotSpot虛擬機中,monitor採用ObjectMonitor實現。

JVM源碼分析之Object.wait/notify實現

每個線程都有兩個ObjectMonitor對象列表,分別為free和used列表,如果當前free列表為空,線程將向全局global list請求分配ObjectMonitor。

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

JVM源碼分析之Object.wait/notify實現

**_WaitSet ** :處於wait狀態的線程,會被加入到wait set;

_EntryList:處於等待鎖block狀態的線程,會被加入到entry set;ObjectWaiter

JVM源碼分析之Object.wait/notify實現

ObjectWaiter對象是雙向鏈表結構,保存了_thread(當前線程)以及當前的狀態TState等數據, 每個等待鎖的線程都會被封裝成ObjectWaiter對象。

wait方法實現

lock.wait()方法最終通過ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);實現:

1、將當前線程封裝成ObjectWaiter對象node;

JVM源碼分析之Object.wait/notify實現

2、通過ObjectMonitor::AddWaiter方法將node添加到_WaitSet列表中;

JVM源碼分析之Object.wait/notify實現

3、通過ObjectMonitor::exit方法釋放當前的ObjectMonitor對象,這樣其它競爭線程就可以獲取該ObjectMonitor對象。

JVM源碼分析之Object.wait/notify實現

4、最終底層的park方法會掛起線程;notify方法實現

lock.notify()方法最終通過ObjectMonitor的void notify(TRAPS)實現:

1、如果當前_WaitSet為空,即沒有正在等待的線程,則直接返回;

2、通過ObjectMonitor::DequeueWaiter方法,獲取_WaitSet列表中的第一個ObjectWaiter節點,實現也很簡單。

這裡需要注意的是,在jdk的notify方法註釋是隨機喚醒一個線程,其實是第一個ObjectWaiter節點

JVM源碼分析之Object.wait/notify實現

3、根據不同的策略,將取出來的ObjectWaiter節點,加入到_EntryList或則通過Atomic::cmpxchg_ptr指令進行自旋操作cxq,具體代碼實現有點長,這裡就不貼了,有興趣的同學可以看objectMonitor::notify方法;notifyAll方法實現

lock.notifyAll()方法最終通過ObjectMonitor的void notifyAll(TRAPS)實現:

通過for循環取出_WaitSet的ObjectWaiter節點,並根據不同策略,加入到_EntryList或則進行自旋操作。

從JVM的方法實現中,可以發現:notify和notifyAll並不會釋放所佔有的ObjectMonitor對象,其實真正釋放ObjectMonitor對象的時間點是在執行monitorexit指令,一旦釋放ObjectMonitor對象了,entry set中ObjectWaiter節點所保存的線程就可以開始競爭ObjectMonitor對象進行加鎖操作了。


分享到:


相關文章: