1. 程式人生 > >wait()和notify()深入剖析

wait()和notify()深入剖析

一、官方文件

對於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