核心有這樣幾個關鍵字:鎖,monitor以及指令。PS:遇到面試官問的問題不是你曾經考慮的問題不要怕,不要緊張。可能隻是說法變了,但是知識還是那些知識!
【1】線程通信執行個體分析
先看一個線程通信例子:
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();
}
}
很常見的synchronized、wait和notify。由同一個lock對象調用wait、notify方法。當線程A執行wait方法時,該線程會被挂起;當線程B執行notify方法時,會喚醒一個被挂起的線程A。
兩個疑問
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指令。
執行
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版本為例。
【2】什麼是monitor?
在HotSpot虛拟機中,
monitor采用ObjectMonitor
實作。
每個線程都有兩個ObjectMonitor對象清單,分别為free和used清單
,如果目前free清單為空,線程将向
全局global list請求配置設定ObjectMonitor
。
ObjectMonitor對象中有兩個隊列:
_WaitSet
和
_EntryList
,用來儲存ObjectWaiter對象清單;
_owner
指向獲得ObjectMonitor對象的線程。
_WaitSet :處于wait狀态的線程,會被加入到wait set;
_EntryList:處于等待鎖block狀态的線程,會被加入到entry set;
【3】ObjectWaiter
ObjectWaiter對象是雙向連結清單結構,儲存了
_thread
(目前線程)以及目前的狀态TState等資料,
每個等待鎖的線程都會被封裝成ObjectWaiter對象
。
【4】wait方法實作原理
lock.wait()方法最終通過
ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);
實作。
1、将目前線程封裝成ObjectWaiter對象node;
2、通過
ObjectMonitor::AddWaiter
方法将node添加到
_WaitSet
清單中;
3、通過
ObjectMonitor::exit
方法釋放目前的ObjectMonitor對象,這樣其它競争線程就可以擷取該ObjectMonitor對象。
4、最終底層的park方法會挂起線程。
【5】notify方法實作原理
lock.notify()
方法最終通過
ObjectMonitor的void notify(TRAPS)
實作。
1、如果目前_WaitSet為空,即沒有正在等待的線程,則直接傳回;
2、通過
ObjectMonitor::DequeueWaiter
方法,擷取
_WaitSet
清單中的第一個ObjectWaiter節點,實作也很簡單。(這裡需要注意的是,在jdk的notify方法注釋是随機喚醒一個線程,其實是第一個ObjectWaiter節點。)
3、根據不同的政策,将取出來的ObjectWaiter節點,加入到_EntryList或則通過
Atomic::cmpxchg_ptr
指令進行自旋操作cxq。
【6】notifyAll方法實作
lock.notifyAll()
方法最終通過
ObjectMonitor的void notifyAll(TRAPS)
實作。
通過for循環取出
_WaitSet
的ObjectWaiter節點,并根據不同政策,加入到_EntryList或則進行自旋操作。