【併發程式設計】Object的wait、notify和notifyAll方法
本部落格系列是學習併發程式設計過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。
併發程式設計系列部落格傳送門
方法簡介
wait方法
wait方法是Object類中的一個方法。呼叫這個方法會讓呼叫執行緒進入waiting狀態,直到另一個執行緒呼叫了當前物件上的notify()或者notifyAll()方法(當然,如果其他執行緒呼叫了該執行緒的interrupt()方法,該執行緒丟擲InterruptedException異常返回)。同時如果當前執行緒已經獲取了鎖資源,呼叫wait方法之後會釋放這個鎖資源。
wait方法還有一個過載方法wait(long time),這個方法會等待time時間,如果在這個時間內沒有其他執行緒來喚醒它的話,這個執行緒會自己喚醒繼續獲得執行機會。
另外需要注意的是,如果呼叫wait()方法的執行緒沒有事先獲取該物件的監視器鎖,則呼叫wait()方法時呼叫執行緒會丟擲IllegalMonitorStateException異常。
notify方法
notify方法會喚醒等待物件監視器的單個執行緒,如果等待物件監視器的有多個執行緒,則選取其中一個執行緒進行喚醒到底選擇喚醒哪個執行緒是任意的,由CPU自己決定。
notify方法還有個兄弟方法notifyAll,這個方法會喚醒所有等待監視器物件的執行緒。
wait-notify模式的典型應用
wait-notify模式的一個典型應用就是可以實現生產者-消費者模式。讓我印象很深是我畢業那年阿里巴巴年校園招聘的一個筆試題:
有一個蘋果箱,有10個人向這個箱子中每次隨機放入一個蘋果,有10個人每次隨機從這個箱子中隨機拿走一個蘋果,同時需要滿足箱子中的蘋果總數不能超過50個。請用程式碼實現上面的場景(不能使用併發集合框架)
現在看來,這道題不就是為wait-notify模式量身打造的一道題目麼。當時水平有限,又急急忙忙的,所以記得當時寫的不太好。這邊重新整理下這個程式碼
public class AppleBox { private int appleCount; public synchronized void putApple() { while (appleCount >= 50) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } appleCount++; String name = Thread.currentThread().getName(); System.out.println("[" + name + "]放入一個,當前盒子中蘋果數:" + appleCount); this.notifyAll(); } public synchronized void takeApple() { while (appleCount <= 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } appleCount--; String name = Thread.currentThread().getName(); System.out.println("[" + name + "]拿走一個,當前盒子中蘋果數:" + appleCount); this.notifyAll(); } private static class AppleTaker implements Runnable { private AppleBox appleBox; public AppleTaker(AppleBox appleBox) { this.appleBox = appleBox; } @Override public void run() { while (true) { appleBox.takeApple(); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } private static class ApplePutter implements Runnable { private AppleBox appleBox; public ApplePutter(AppleBox appleBox) { this.appleBox = appleBox; } @Override public void run() { while (true) { appleBox.putApple(); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { AppleBox appleBox = new AppleBox(); for (int i = 0; i < 20; i++) { Thread t = new Thread(new ApplePutter(appleBox)); t.setName("ApplePutter:" + i); t.start(); } for (int i = 0; i < 20; i++) { Thread t = new Thread(new AppleTaker(appleBox)); t.setName("AppleTaker:" + i); t.start(); } } }
執行結果如下:
[ApplePutter:0]放入一個,當前盒子中蘋果數:1
[ApplePutter:1]放入一個,當前盒子中蘋果數:2
[ApplePutter:5]放入一個,當前盒子中蘋果數:3
[ApplePutter:9]放入一個,當前盒子中蘋果數:4
[ApplePutter:13]放入一個,當前盒子中蘋果數:5
[ApplePutter:2]放入一個,當前盒子中蘋果數:6
[ApplePutter:6]放入一個,當前盒子中蘋果數:7
[ApplePutter:10]放入一個,當前盒子中蘋果數:8
[ApplePutter:17]放入一個,當前盒子中蘋果數:9
[ApplePutter:14]放入一個,當前盒子中蘋果數:10
[ApplePutter:18]放入一個,當前盒子中蘋果數:11
[ApplePutter:3]放入一個,當前盒子中蘋果數:12
[ApplePutter:7]放入一個,當前盒子中蘋果數:13
[ApplePutter:11]放入一個,當前盒子中蘋果數:14
[ApplePutter:8]放入一個,當前盒子中蘋果數:15
[ApplePutter:15]放入一個,當前盒子中蘋果數:16
[ApplePutter:19]放入一個,當前盒子中蘋果數:17
[ApplePutter:4]放入一個,當前盒子中蘋果數:18
[AppleTaker:3]拿走一個,當前盒子中蘋果數:17
[ApplePutter:12]放入一個,當前盒子中蘋果數:18
[AppleTaker:1]拿走一個,當前盒子中蘋果數:17
[AppleTaker:5]拿走一個,當前盒子中蘋果數:16
[ApplePutter:16]放入一個,當前盒子中蘋果數:17
[AppleTaker:0]拿走一個,當前盒子中蘋果數:16
[AppleTaker:12]拿走一個,當前盒子中蘋果數:15
[AppleTaker:8]拿走一個,當前盒子中蘋果數:14
[AppleTaker:16]拿走一個,當前盒子中蘋果數:13
[AppleTaker:7]拿走一個,當前盒子中蘋果數:12
[AppleTaker:11]拿走一個,當前盒子中蘋果數:11
[AppleTaker:19]拿走一個,當前盒子中蘋果數:10
[AppleTaker:9]拿走一個,當前盒子中蘋果數:9
[AppleTaker:13]拿走一個,當前盒子中蘋果數:8
[AppleTaker:2]拿走一個,當前盒子中蘋果數:7
[AppleTaker:6]拿走一個,當前盒子中蘋果數:6
[AppleTaker:10]拿走一個,當前盒子中蘋果數:5
[AppleTaker:14]拿走一個,當前盒子中蘋果數:4
[AppleTaker:4]拿走一個,當前盒子中蘋果數:3
[AppleTaker:15]拿走一個,當前盒子中蘋果數:2
[AppleTaker:18]拿走一個,當前盒子中蘋果數:1
[AppleTaker:17]拿走一個,當前盒子中蘋果數:0
[ApplePutter:0]放入一個,當前盒子中蘋果數:1
[ApplePutter:1]放入一個,當前盒子中蘋果數:2
[ApplePutter:5]放入一個,當前盒子中蘋果數:3
[ApplePutter:9]放入一個,當前盒子中蘋果數:4
[ApplePutter:13]放入一個,當前盒子中蘋果數:5
[ApplePutter:17]放入一個,當前盒子中蘋果數:6
[ApplePutter:2]放入一個,當前盒子中蘋果數:7
[ApplePutter:6]放入一個,當前盒子中蘋果數:8
[ApplePutter:10]放入一個,當前盒子中蘋果數:9
[ApplePutter:14]放入一個,當前盒子中蘋果數:10
[ApplePutter:18]放入一個,當前盒子中蘋果數:11
[ApplePutter:3]放入一個,當前盒子中蘋果數:12
[ApplePutter:7]放入一個,當前盒子中蘋果數:13
[ApplePutter:11]放入一個,當前盒子中蘋果數:14
[ApplePutter:15]放入一個,當前盒子中蘋果數:15
[ApplePutter:19]放入一個,當前盒子中蘋果數:16
[AppleTaker:3]拿走一個,當前盒子中蘋果數:15
[ApplePutter:4]放入一個,當前盒子中蘋果數:16
[ApplePutter:8]放入一個,當前盒子中蘋果數:17
[ApplePutter:12]放入一個,當前盒子中蘋果數:18
wait-notify模式的經典寫法
生產者和消費者的邏輯都可以統一抽象成以下幾個步驟:
- step1:獲得物件的鎖;
- step2:迴圈判斷是否需要進行生產活動,如果不需要進行生產就呼叫wait方法,暫停當前執行緒;如果需要進行生產活動,進行對應的生產活動;
- step3:通知等待執行緒
虛擬碼如下:
synchronized(物件) {
while(條件){
物件.wait();
}
//進行生產或者消費活動
doSomething();
物件.notifyAll();
}