執行緒間協作——wait、notify、notifyAll

gary-liu發表於2017-03-14

在 Java 中,可以通過配合呼叫 Object 物件的 wait() 方法和 notify()方法或 notifyAll() 方法來實現執行緒間的通訊。線上程中呼叫 wait() 方法,將阻塞等待其他執行緒的通知(其他執行緒呼叫 notify() 方法或 notifyAll() 方法),線上程中呼叫 notify() 方法或 notifyAll() 方法,將通知其他執行緒從 wait() 方法處返回。

wait()

該方法用來將當前執行緒置入休眠狀態,直到接到通知或被中斷為止。在呼叫 wait()之前,執行緒必須要獲得該物件的物件級別鎖,即只能在同步方法或同步塊中呼叫 wait()方法。進入 wait()方法後,當前執行緒釋放鎖。

notify()

該方法也要在同步方法或同步塊中呼叫,即在呼叫前,執行緒也必須要獲得該物件的物件級別鎖。
該方法用來通知那些可能等待該物件的物件鎖的其他執行緒。如果有多個執行緒等待,則執行緒規劃器任意挑選出其中一個 wait()狀態的執行緒來發出通知,並使它等待獲取該物件的物件鎖(notify 後,當前執行緒不會馬上釋放該物件鎖,wait 所在的執行緒並不能馬上獲取該物件鎖,要等到程式退出 synchronized 程式碼塊後,當前執行緒才會釋放鎖,wait所在的執行緒也才可以獲取該物件鎖),但不驚動其他同樣在等待被該物件notify的執行緒們

notifyAll()

該方法與 notify ()方法的工作方式相同,重要的一點差異是:
notifyAll 使所有原來在該物件上 wait 的執行緒統統退出 wait 的狀態(即全部被喚醒,不再等待 notify 或 notifyAll,但由於此時還沒有獲取到該物件鎖,因此還不能繼續往下執行),變成等待獲取該物件上的鎖,一旦該物件鎖被釋放(notifyAll 執行緒退出呼叫了 notifyAll 的 synchronized 程式碼塊的時候),他們就會去競爭。如果其中一個執行緒獲得了該物件鎖,它就會繼續往下執行,在它退出 synchronized 程式碼塊,釋放鎖後,其他的已經被喚醒的執行緒將會繼續競爭獲取該鎖,一直進行下去,直到所有被喚醒的執行緒都執行完畢。

應該在while迴圈,而不是if語句中呼叫wait。if語句存在一些微妙的小問題,導致即使條件沒被滿足,你的執行緒你也有可能被錯誤地喚醒。所以如果你不線上程被喚醒後再次使用while迴圈檢查喚醒條件是否被滿足,你的程式就有可能會出錯。在while迴圈裡使用wait的目的,是線上程被喚醒的前後都持續檢查條件是否被滿足。如果條件並未改變,wait被呼叫之前notify的喚醒通知就來了,那麼這個執行緒並不能保證被喚醒,有可能會導致死鎖問題。參考《Effective Java》

示例

我之前寫過一篇文章 執行緒執行順序——CountDownLatch、join()、執行緒池 討論的是讓一個執行緒晚於其他執行緒最後執行。我想用wait和notify寫個例子讓一個執行緒先於其他執行緒執行。

程式碼場景:Worker類和Boss類都實現Runnable介面,但是老闆先安排完工作後,工人才能開始工作。

程式碼實現:

class Worker implements Runnable{

    private String name;
    private Object object;

    public Worker(String name,Object object){
        this.object = object;
        this.name = name;
    }

    public void run(){
        synchronized(object){
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " is working");
        }
    }
}

class Boss implements Runnable{

    private String name;
    private Object object;

    public Boss(String name, Object object){
        this.name = name;
        this.object = object;
    }

    public void run(){
        synchronized(object){
            System.out.println(name + " has arranged the work");
            object.notifyAll();
            //object.notify();
        }
    }
}

public static void main(String[] args) throws Exception{

        Object object = new Object();

        new Thread(new Worker("work1", object)).start();
        new Thread(new Worker("work2", object)).start();
        new Thread(new Worker("work3", object)).start();

        new Thread(new Boss("boss", object)).start();


    }

執行結果:

boss has arranged the work
work3 is working
work2 is working
work1 is working

從上面的程式碼中可以看到,Boss類使用notifyAll()方法,3個worker執行緒都會執行,如果換成 notify()方法,則只有一個worker執行緒會執行。

完整程式碼在github的 https://github.com/lzx2011/java-scaffold 中的thread包中的 WaitAndNotify 類中。

應用

比如可以用wait和notify實現生產者和消費者,這裡不細說了,有空再寫下生產者和消費者吧。

總結

  1. 可以使用wait和notify函式來實現執行緒間通訊。

  2. 在synchronized的函式或物件裡使用wait、notify和notifyAll,否則Java虛擬機器會生成 IllegalMonitorStateException。

  3. 在while迴圈裡而不是if語句下使用wait。這樣,迴圈會線上程睡眠前後都檢查wait的條件,並在條件實際上並未改變的情況下處理喚醒通知。

  4. 在多執行緒間共享的物件(在生產者消費者模型裡即緩衝區佇列)上使用wait。

參考資料

執行緒間協作:wait、notify、notifyAll
如何在 Java 中正確使用 wait, notify 和 notifyAll – 以生產者消費者模型為例

相關文章