wait()和notify()深入剖析
阿新 • • 發佈:2018-11-20
一、官方文件
對於wait()和notify()的理解,還是要從jdk官方文件中開始,在Object類方法中有:對於wait()和notify()的理解,還是要從jdk官方文件中開始,在Object類方法中有:
void notify() Wakes up a single thread that is waiting on this object’s monitor. 譯:喚醒在此物件監視器上等待的單個執行緒 void notifyAll() Wakes up all threads that are waiting on this object’s monitor. 譯:喚醒在此物件監視器上等待的所有執行緒 void wait( ) Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object. 譯:導致當前的執行緒等待,直到其他執行緒呼叫此物件的notify( ) 方法或 notifyAll( ) 方法 void wait(long timeout) Causes the current thread to wait until either another thread invokes the notify( ) method or the notifyAll( ) method for this object, or a specified amount of time has elapsed. 譯:導致當前的執行緒等待,直到其他執行緒呼叫此物件的notify() 方法或 notifyAll() 方法,或者指定的時間過完。 void wait(long timeout, int nanos) Causes the current thread to wait until another thread invokes the notify( ) method or the notifyAll( ) method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed. 譯:導致當前的執行緒等待,直到其他執行緒呼叫此物件的notify( ) 方法或 notifyAll( ) 方法,或者其他執行緒打斷了當前執行緒,或者指定的時間過完。
上面是官方文件的簡介,下面我們根據官方文件總結一下:
- wait( ),notify( ),notifyAll( )都不屬於Thread類,而是屬於Object基礎類,也就是每個物件都有wait( ),notify( ),notifyAll( ) 的功能,因為每個物件都有鎖,鎖是每個物件的基礎,當然操作鎖的方法也是最基礎了。
- 當需要呼叫以上的方法的時候,一定要對競爭資源進行加鎖,如果不加鎖的話,則會報 IllegalMonitorStateException 異常
- 當想要呼叫wait( )進行執行緒等待時,必須要取得這個鎖物件的控制權(物件監視器),一般是放到synchronized(obj)程式碼中。
- 在while迴圈裡而不是if語句下使用wait,這樣,會線上程暫停恢復後都檢查wait的條件,並在條件實際上並未改變的情況下處理喚醒通知
- 呼叫obj.wait( )釋放了obj的鎖,否則其他執行緒也無法獲得obj的鎖,也就無法在synchronized(obj){ obj.notify() } 程式碼段內喚醒A。
- notify( )方法只會通知等待佇列中的第一個相關執行緒(不會通知優先順序比較高的執行緒)
- notifyAll( )通知所有等待該競爭資源的執行緒(也不會按照執行緒的優先順序來執行)
- 假設有三個執行緒執行了obj.wait( ),那麼obj.notifyAll( )則能全部喚醒tread1,thread2,thread3,但是要繼續執行obj.wait()的下一條語句,必須獲得obj鎖,因此,tread1,thread2,thread3只有一個有機會獲得鎖繼續執行,例如tread1,其餘的需要等待thread1釋放obj鎖之後才能繼續執行。
- 當呼叫obj.notify/notifyAll後,呼叫執行緒依舊持有obj鎖,因此,thread1,thread2,thread3雖被喚醒,但是仍無法獲得obj鎖。直到呼叫執行緒退出synchronized塊,釋放obj鎖後,thread1,thread2,thread3中的一個才有機會獲得鎖繼續執行。
二、wait()和notify()的通常用法
Java多執行緒開發中,我們常用到wait()和notify()方法來實現執行緒間的協作,簡單的說步驟如下:
1. A執行緒取得鎖,執行wait(),釋放鎖;
2. B執行緒取得鎖,完成業務後執行notify(),再釋放鎖;
3. B執行緒釋放鎖之後,A執行緒取得鎖,繼續執行wait()之後的程式碼;
三、生產者與消費者模式
假設有一個公共的容量有限的池子,有兩種人,一種是生產者,另一種是消費者。需要滿足如下條件:
1、生產者產生資源往池子裡新增,前提是池子沒有滿,如果池子滿了,則生產者暫停生產,直到自己的生成能放下池子。
2、消費者消耗池子裡的資源,前提是池子的資源不為空,否則消費者暫停消耗,進入等待直到池子裡有資源數滿足自己的需求。
運用wait()和notify()實現如下:
抽象倉庫類 public interface AbstractStorage { void consume(int num); void produce(int num); }
倉庫類 /** * 生產者和消費者的問題 * wait、notify/notifyAll() 實現 */ public class Storage1 implements AbstractStorage { //倉庫最大容量 private final int MAX_SIZE = 100; //倉庫儲存的載體 private LinkedList list = new LinkedList(); //生產產品 public void produce(int num){ //同步 synchronized (list){ //倉庫剩餘的容量不足以存放即將要生產的數量,暫停生產 while(list.size()+num > MAX_SIZE){ System.out.println("【要生產的產品數量】:" + num + "\t【庫存量】:" + list.size() + "\t暫時不能執行生產任務!"); try { //條件不滿足,生產阻塞 list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i=0;i<num;i++){ list.add(new Object()); } System.out.println("【已經生產產品數】:" + num + "\t【現倉儲量為】:" + list.size()); list.notifyAll(); } } //消費產品 public void consume(int num){ synchronized (list){ //不滿足消費條件 while(num > list.size()){ System.out.println("【要消費的產品數量】:" + num + "\t【庫存量】:" + list.size() + "\t暫時不能執行生產任務!"); try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //消費條件滿足,開始消費 for(int i=0;i<num;i++){ list.remove(); } System.out.println("【已經消費產品數】:" + num + "\t【現倉儲量為】:" + list.size()); list.notifyAll(); } } }
生產者 public class Producer extends Thread{ //每次生產的數量 private int num ; //所屬的倉庫 public AbstractStorage abstractStorage; public Producer(AbstractStorage abstractStorage){ this.abstractStorage = abstractStorage; } public void setNum(int num){ this.num = num; } // 執行緒run函式 @Override public void run() { produce(num); } // 呼叫倉庫Storage的生產函式 public void produce(int num) { abstractStorage.produce(num); } }
消費者 public class Consumer extends Thread{ // 每次消費的產品數量 private int num; // 所在放置的倉庫 private AbstractStorage abstractStorage1; // 建構函式,設定倉庫 public Consumer(AbstractStorage abstractStorage1) { this.abstractStorage1 = abstractStorage1; } // 執行緒run函式 public void run() { consume(num); } // 呼叫倉庫Storage的生產函式 public void consume(int num) { abstractStorage1.consume(num); } public void setNum(int num){ this.num = num; } }
測試類 public class Test{ public static void main(String[] args) { // 倉庫物件 AbstractStorage abstractStorage = new Storage1(); // 生產者物件 Producer p1 = new Producer(abstractStorage); Producer p2 = new Producer(abstractStorage); Producer p3 = new Producer(abstractStorage); Producer p4 = new Producer(abstractStorage); Producer p5 = new Producer(abstractStorage); Producer p6 = new Producer(abstractStorage); Producer p7 = new Producer(abstractStorage); // 消費者物件 Consumer c1 = new Consumer(abstractStorage); Consumer c2 = new Consumer(abstractStorage); Consumer c3 = new Consumer(abstractStorage); // 設定生產者產品生產數量 p1.setNum(10); p2.setNum(10); p3.setNum(10); p4.setNum(10); p5.setNum(10); p6.setNum(10); p7.setNum(80); // 設定消費者產品消費數量 c1.setNum(50); c2.setNum(20); c3.setNum(30); // 執行緒開始執行 c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); p6.start(); p7.start(); } }
執行結果: 【要消費的產品數量】:50 【庫存量】:0 暫時不能執行生產任務! 【要消費的產品數量】:20 【庫存量】:0 暫時不能執行生產任務! 【要消費的產品數量】:30 【庫存量】:0 暫時不能執行生產任務! 【已經生產產品數】:10 【現倉儲量為】:10 【要消費的產品數量】:30 【庫存量】:10 暫時不能執行生產任務! 【要消費的產品數量】:20 【庫存量】:10 暫時不能執行生產任務! 【要消費的產品數量】:50 【庫存量】:10 暫時不能執行生產任務! 【已經生產產品數】:10 【現倉儲量為】:20 【已經生產產品數】:10 【現倉儲量為】:30 【要消費的產品數量】:50 【庫存量】:30 暫時不能執行生產任務! 【已經消費產品數】:20 【現倉儲量為】:10 【要消費的產品數量】:30 【庫存量】:10 暫時不能執行生產任務! 【已經生產產品數】:10 【現倉儲量為】:20 【要消費的產品數量】:50 【庫存量】:20 暫時不能執行生產任務! 【要消費的產品數量】:30 【庫存量】:20 暫時不能執行生產任務! 【已經生產產品數】:10 【現倉儲量為】:30 【已經消費產品數】:30 【現倉儲量為】:0 【要消費的產品數量】:50 【庫存量】:0 暫時不能執行生產任務! 【已經生產產品數】:10 【現倉儲量為】:10 【要消費的產品數量】:50 【庫存量】:10 暫時不能執行生產任務! 【已經生產產品數】:80 【現倉儲量為】:90 【已經消費產品數】:50 【現倉儲量為】:40