Java的wait(), notify()和notifyAll()使用小結

wait(),notify()和notifyAll()都是java.lang.Object的方法:html

wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.java

notify(): Wakes up a single thread that is waiting on this object's monitor.緩存

notifyAll(): Wakes up all threads that are waiting on this object's monitor.多線程

 

這三個方法,都是Java語言提供的實現線程間阻塞(Blocking)和控制進程內調度(inter-process communication)的底層機制。在解釋如何使用前,先說明一下兩點:this

1. 正如Java內任何對象都能成爲鎖(Lock)同樣,任何對象也都能成爲條件隊列(Condition queue)。而這個對象裏的wait(), notify()和notifyAll()則是這個條件隊列的固有(intrinsic)的方法。spa

2. 一個對象的固有鎖和它的固有條件隊列是相關的,爲了調用對象X內條件隊列的方法,你必須得到對象X的鎖。這是由於等待狀態條件的機制和保證狀態連續性的機制是緊密的結合在一塊兒的。線程

(An object's intrinsic lock and its intrinsic condition queue are related: in order to call any of the condition queue methods on object X, you must hold the lock on X. This is because the mechanism for waiting for state-based conditions is necessarily tightly bound to the mechanism fo preserving state consistency)code

 

根據上述兩點,在調用wait(), notify()或notifyAll()的時候,必須先得到鎖,且狀態變量須由該鎖保護,而固有鎖對象與固有條件隊列對象又是同一個對象。也就是說,要在某個對象上執行wait,notify,先必須鎖定該對象,而對應的狀態變量也是由該對象鎖保護的。htm

 

知道怎麼使用後,咱們來問下面的問題:對象

1. 執行wait, notify時,不得到鎖會如何?

請看代碼:

public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        obj.wait();
        obj.notifyAll();
}

執行以上代碼,會拋出java.lang.IllegalMonitorStateException的異常。

 

2. 執行wait, notify時,不得到該對象的鎖會如何?

請看代碼:

    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        Object lock = new Object();
        synchronized (lock) {
            obj.wait();
            obj.notifyAll();
        }
    }

執行代碼,一樣會拋出java.lang.IllegalMonitorStateException的異常。

 

3. 爲何在執行wait, notify時,必須得到該對象的鎖?

這是由於,若是沒有鎖,wait和notify有可能會產生競態條件(Race Condition)。考慮如下生產者和消費者的情景:

1.1生產者檢查條件(如緩存滿了)-> 1.2生產者必須等待

2.1消費者消費了一個單位的緩存 -> 2.2從新設置了條件(如緩存沒滿) -> 2.3調用notifyAll()喚醒生產者

咱們但願的順序是: 1.1->1.2->2.1->2.2->2.3

但在多線程狀況下,順序有多是 1.1->2.1->2.2->2.3->1.2。也就是說,在生產者還沒wait以前,消費者就已經notifyAll了,這樣的話,生產者會一直等下去。

因此,要解決這個問題,必須在wait和notifyAll的時候,得到該對象的鎖,以保證同步。

請看如下利用wait,notify實現的一個生產者、一個消費者和一個單位的緩存的簡單模型:

public class QueueBuffer {
    int n;
    boolean valueSet = false;

    synchronized int get() {
        if (!valueSet)
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println("InterruptedException caught");
            }
        System.out.println("Got: " + n);
        valueSet = false;
        notify();
        return n;
    }

    synchronized void put(int n) {
        if (valueSet)
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println("InterruptedException caught");
            }
        this.n = n;
        valueSet = true;
        System.out.println("Put: " + n);
        notify();
    }
}
public class Producer implements Runnable {
    
    private QueueBuffer q;

    Producer(QueueBuffer q) {
        this.q = q;
        new Thread(this, "Producer").start();
    }

    public void run() {
        int i = 0;
        while (true) {
            q.put(i++);
        }
    }

}
public class Consumer implements Runnable {
    
    private QueueBuffer q;

    Consumer(QueueBuffer q) {
        this.q = q;
        new Thread(this, "Consumer").start();
    }

    public void run() {
        while (true) {
            q.get();
        }
    }

}
public class Main {

    public static void main(String[] args) {
        QueueBuffer q = new QueueBuffer(); 
        new Producer(q); 
        new Consumer(q); 
        System.out.println("Press Control-C to stop."); 
    }

}

 

因此,JVM經過在執行的時候拋出IllegalMonitorStateException的異常,來確保wait, notify時,得到了對象的鎖,從而消除隱藏的Race Condition。

 

最後來看看一道題:寫一個多線程程序,交替輸出1,2,1,2,1,2......

利用wait, notify解決:

 1 public class OutputThread implements Runnable {
 2 
 3     private int num;
 4     private Object lock;
 5     
 6     public OutputThread(int num, Object lock) {
 7         super();
 8         this.num = num;
 9         this.lock = lock;
10     }
11 
12     public void run() {
13         try {
14             while(true){
15                 synchronized(lock){
16                     lock.notifyAll();
17                     lock.wait();
18                     System.out.println(num);
19                 }
20             }
21         } catch (InterruptedException e) {
22             // TODO Auto-generated catch block
23             e.printStackTrace();
24         }
25         
26     }
27     
28     public static void main(String[] args){
29         final Object lock = new Object();
30         
31         Thread thread1 = new Thread(new OutputThread(1,lock));
32         Thread thread2 = new Thread(new OutputThread(2, lock));
33         
34         thread1.start();
35         thread2.start();
36     }
37 
38 }

 

Java Concurrency in Practice》裏的第14章,對wait, notify有更加詳細的介紹。

參考:

http://javarevisited.blogspot.hk/2011/05/wait-notify-and-notifyall-in-java.html

http://www.ticmy.com/?p=219

相關文章
相關標籤/搜索