寫在前面
Wait
和 Notify
是 Java
面試中常見的問題,但是在平時工作中可能不常見到。大家或多或少知道些背景知識,例如二者均為 Object
類的方法,而不是 Thread
特有的(因為鎖
是每個物件都具有的特性,因此操作鎖的方法也緊跟物件,沒毛病),且都只能在同步程式碼塊
中呼叫(即前提是先獲得物件的監視器鎖
,一般來說在 synchronized
程式碼塊中使用),否則丟擲異常 IllegalMonitorStateException
。
Wait
會掛起自己讓出 CPU
時間片,並將自身加入鎖定物件的 Wait Set
中,釋放物件的監視器鎖(monitor)
讓其他執行緒可以獲得,直到其他執行緒呼叫此物件的 notify( )
方法或 notifyAll( )
方法,自身才能被喚醒(這裡有個特殊情況就是 Wait
可以增加等待時間);Notify
方法則會釋放監視器鎖的同時,喚醒物件 Wait Set
中等待的執行緒,順序是隨機的不確定。
Wait Set
虛擬機器規範
中定義了一個 Wait Set
的概念,但至於其具體是什麼樣的資料結構規範沒有強制規定,意味著不同的廠商可以自行實現,但不管怎樣,執行緒呼叫了某個物件的 Wait
方法,就會被加入該物件的 Wait Set
中
Demo 程式碼
下面通過一段 demo
來解釋 Wait
和 Notify
的功能
import java.util.concurrent.TimeUnit;
public class WaitNotify {
public static void main(String[] args) {
final Object A = new Object();
final Object B = new Object();
Thread t1 = new Thread("t1-thread") {
@Override
public void run() {
synchronized (A) {
System.out.println(Thread.currentThread().getName() + "拿到 A 的監視器鎖");
System.out.println(Thread.currentThread().getName() + "嘗試獲取 B 的監視器鎖");
try {
System.out.println(Thread.currentThread().getName() + "休眠 2s,不釋放 A 的監視器鎖");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "掛起自己,釋放 A 的監視器鎖");
A.wait();
System.out.println(Thread.currentThread().getName() + "被喚醒,等待獲取 B 的監視器鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println(Thread.currentThread().getName() + "拿到 B 的監視器鎖");
B.notify();
}
}
}
};
Thread t2 = new Thread("t2-thread") {
@Override
public void run() {
synchronized (B) {
System.out.println(Thread.currentThread().getName() + "拿到 B 的監視器鎖");
System.out.println(Thread.currentThread().getName() + "嘗試獲取 A 的監視器鎖");
synchronized (A) {
System.out.println(Thread.currentThread().getName() + "拿到 A 的監視器鎖");
try {
System.out.println(Thread.currentThread().getName() + "休眠 2s,不釋放 A 的監視器鎖");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "掛起自己,釋放 A 的監視器鎖,喚醒 t0");
A.notify();
}
try {
System.out.println(Thread.currentThread().getName() + "休眠 2s,不釋放 B 的監視器鎖");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "掛起自己,釋放 B 的監視器鎖");
B.wait();
System.out.println(Thread.currentThread().getName() + "被喚醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
t2.start();
}
}
複製程式碼
輸出結果
t1-thread拿到 A 的監視器鎖
t2-thread拿到 B 的監視器鎖
t1-thread嘗試獲取 B 的監視器鎖
t2-thread嘗試獲取 A 的監視器鎖
t1-thread休眠 2s,不釋放 A 的監視器鎖
t1-thread掛起自己,釋放 A 的監視器鎖
t2-thread拿到 A 的監視器鎖
t2-thread休眠 2s,不釋放 A 的監視器鎖
t2-thread掛起自己,釋放 A 的監視器鎖,喚醒 t0
t2-thread休眠 2s,不釋放 B 的監視器鎖
t1-thread被喚醒,等待獲取 B 的監視器鎖
t2-thread掛起自己,釋放 B 的監視器鎖
t1-thread拿到 B 的監視器鎖
t2-thread被喚醒
Process finished with exit code 0
複製程式碼
時序圖
寫在最後
這是一個不定時更新的、披著程式設計師外衣的文青小號,歡迎關注。