如何在 Java 中正確使用 wait, notify 和 notifyAll

在 Java 中能夠用 wait、notify 和 notifyAll 來實現線程間的通訊。。舉個例子,若是你的Java程序中有兩個線程——即生產者和消費者,那麼生產者能夠通知消費者,讓消費者開始消耗數據,由於隊列緩 衝區中有內容待消費(不爲空)。相應的,消費者能夠通知生產者能夠開始生成更多的數據,由於當它消耗掉某些數據後緩衝區再也不爲滿。java

wait, notify 和 notifyAll,這些在多線程中被常常用到的保留關鍵字,在實際開發的時候不少時候卻並無被你們重視。本文對這些關鍵字的使用進行了描述。程序員

在 Java 中能夠用 wait、notify 和 notifyAll 來實現線程間的通訊。。舉個例子,若是你的Java程序中有兩個線程——即生產者和消費者,那麼生產者能夠通知消費者,讓消費者開始消耗數據,由於隊列緩 衝區中有內容待消費(不爲空)。相應的,消費者能夠通知生產者能夠開始生成更多的數據,由於當它消耗掉某些數據後緩衝區再也不爲滿。面試

咱們能夠利用wait()來讓一個線程在某些條件下暫停運行。例如,在生產者消費者模型中,生產者線程在緩衝區爲滿的時候,消費者在緩衝區爲空的時 候,都應該暫停運行。若是某些線程在等待某些條件觸發,那當那些條件爲真時,你能夠用 notify 和 notifyAll 來通知那些等待中的線程從新開始運行。不一樣之處在於,notify 僅僅通知一個線程,而且咱們不知道哪一個線程會收到通知,然而 notifyAll 會通知全部等待中的線程。換言之,若是隻有一個線程在等待一個信號燈,notify和notifyAll都會通知到這個線程。但若是多個線程在等待這個信 號燈,那麼notify只會通知到其中一個,而其它線程並不會收到任何通知,而notifyAll會喚醒全部等待中的線程。安全

在這篇文章中你將會學到如何使用 wait、notify 和 notifyAll 來實現線程間的通訊,從而解決生產者消費者問題。若是你想要更深刻地學習Java中的多線程同步問題,我強烈推薦閱讀Brian Goetz所著的《Java Concurrency in Practice | Java 併發實踐》,不讀這本書你的 Java 多線程征程就不完整哦!這是我最向Java開發者推薦的書之一。多線程

如何使用Wait併發

儘管關於wait和notify的概念很基礎,它們也都是Object類的函數,但用它們來寫代碼卻並不簡單。若是你在面試中讓應聘者來手寫代碼, 用wait和notify解決生產者消費者問題,我幾乎能夠確定他們中的大多數都會無所適從或者犯下一些錯誤,例如在錯誤的地方使用 synchronized 關鍵詞,沒有對正確的對象使用wait,或者沒有遵循規範的代碼方法。說實話,這個問題對於不常使用它們的程序員來講確實使人感受比較頭疼。dom

第一個問題就是,咱們怎麼在代碼裏使用wait()呢?由於wait()並非Thread類下的函數,咱們並不能使用 Thread.call()。事實上不少Java程序員都喜歡這麼寫,由於它們習慣了使用Thread.sleep(),因此他們會試圖使用wait() 來達成相同的目的,但很快他們就會發現這並不能順利解決問題。正確的方法是對在多線程間共享的那個Object來使用wait。在生產者消費者問題中,這 個共享的Object就是那個緩衝區隊列。ide

第二個問題是,既然咱們應該在synchronized的函數或是對象裏調用wait,那哪一個對象應該被synchronized呢?答案是,那個 你但願上鎖的對象就應該被synchronized,即那個在多個線程間被共享的對象。在生產者消費者問題中,應該被synchronized的就是那個 緩衝區隊列。(我以爲這裏是英文原文有問題……原本那個句末就不該該是問號否則不太通……)函數

如何在 Java 中正確使用 wait, notify 和 notifyAll – 以生產者消費者模型爲例

永遠在循環(loop)裏調用 wait 和 notify,不是在 If 語句oop

如今你知道wait應該永遠在被synchronized的背景下和那個被多線程共享的對象上調用,下一個必定要記住的問題就是,你應該永遠在 while循環,而不是if語句中調用wait。由於線程是在某些條件下等待的——在咱們的例子裏,即「若是緩衝區隊列是滿的話,那麼生產者線程應該等 待」,你可能直覺就會寫一個if語句。但if語句存在一些微妙的小問題,致使即便條件沒被知足,你的線程你也有可能被錯誤地喚醒。因此若是你不在線程被喚 醒後再次使用while循環檢查喚醒條件是否被知足,你的程序就有可能會出錯——例如在緩衝區爲滿的時候生產者繼續生成數據,或者緩衝區爲空的時候消費者 開始小號數據。因此記住,永遠在while循環而不是if語句中使用wait!我會推薦閱讀《Effective Java》,這是關於如何正確使用wait和notify的最好的參考資料。

基於以上認知,下面這個是使用wait和notify函數的規範代碼模板:

1 // The standard idiom for calling the wait method in Java 
2 synchronized (sharedObject) { 
3     while (condition) { 
4 sharedObject.wait(); 
5         // (Releases lock, and reacquires on wakeup) 
6     } 
7     // do action based upon condition e.g. take or put into queue 
8 }

就像我以前說的同樣,在while循環裏使用wait的目的,是在線程被喚醒的先後都持續檢查條件是否被知足。若是條件並未改變,wait被調用以前notify的喚醒通知就來了,那麼這個線程並不能保證被喚醒,有可能會致使死鎖問題。

Java wait(), notify(), notifyAll() 範例

下面咱們提供一個使用wait和notify的範例程序。在這個程序裏,咱們使用了上文所述的一些代碼規範。咱們有兩個線程,分別名爲 PRODUCER(生產者)和CONSUMER(消費者),他們分別繼承了了Producer和Consumer類,而Producer和 Consumer都繼承了Thread類。Producer和Consumer想要實現的代碼邏輯都在run()函數內。Main線程開始了生產者和消費 者線程,並聲明瞭一個LinkedList做爲緩衝區隊列(在Java中,LinkedList實現了隊列的接口)。生產者在無限循環中持續往 LinkedList裏插入隨機整數直到LinkedList滿。咱們在while(queue.size == maxSize)循環語句中檢查這個條件。請注意到咱們在作這個檢查條件以前已經在隊列對象上使用了synchronized關鍵詞,於是其它線程不能在 咱們檢查條件時改變這個隊列。若是隊列滿了,那麼PRODUCER線程會在CONSUMER線程消耗掉隊列裏的任意一個整數,並用notify來通知 PRODUCER線程以前持續等待。在咱們的例子中,wait和notify都是使用在同一個共享對象上的。

 1 import java.util.LinkedList; 
 2 import java.util.Queue; 
 3 import java.util.Random; 
 4 /** 
 5 * Simple Java program to demonstrate How to use wait, notify and notifyAll() 
 6 * method in Java by solving producer consumer problem. 
 7 * 
 8 * @author Javin Paul 
 9 */ 
10 public class ProducerConsumerInJava { 
11 public static void main(String args[]) { 
12   System.out.println("How to use wait and notify method in Java"); 
13   System.out.println("Solving Producer Consumper Problem"); 
14   Queue<Integer> buffer = new LinkedList<>(); 
15   int maxSize = 10; 
16   Thread producer = new Producer(buffer, maxSize, "PRODUCER"); 
17   Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); 
18   producer.start(); consumer.start(); } 
19 } 
20 /** 
21 * Producer Thread will keep producing values for Consumer 
22 * to consumer. It will use wait() method when Queue is full 
23 * and use notify() method to send notification to Consumer 
24 * Thread. 
25 * 
26 * @author WINDOWS 8 
27 * 
28 */ 
29 class Producer extends Thread 
30 { private Queue<Integer> queue; 
31   private int maxSize; 
32   public Producer(Queue<Integer> queue, int maxSize, String name){ 
33    super(name); this.queue = queue; this.maxSize = maxSize; 
34   } 
35   @Override public void run() 
36   { 
37    while (true) 
38     { 
39      synchronized (queue) { 
40       while (queue.size() == maxSize) { 
41        try { 
42         System.out .println("Queue is full, " + "Producer thread waiting for " + "consumer to take something from queue"); 
43         queue.wait(); 
44        } catch (Exception ex) { 
45         ex.printStackTrace(); } 
46        } 
47        Random random = new Random(); 
48        int i = random.nextInt(); 
49        System.out.println("Producing value : " + i); queue.add(i); queue.notifyAll(); 
50       } 
51      } 
52     } 
53    } 
54 /** 
55 * Consumer Thread will consumer values form shared queue. 
56 * It will also use wait() method to wait if queue is 
57 * empty. It will also use notify method to send 
58 * notification to producer thread after consuming values 
59 * from queue. 
60 * 
61 * @author WINDOWS 8 
62 * 
63 */ 
64 class Consumer extends Thread { 
65   private Queue<Integer> queue; 
66   private int maxSize; 
67   public Consumer(Queue<Integer> queue, int maxSize, String name){ 
68    super(name); 
69    this.queue = queue; 
70    this.maxSize = maxSize; 
71   } 
72   @Override public void run() { 
73    while (true) { 
74     synchronized (queue) { 
75      while (queue.isEmpty()) { 
76       System.out.println("Queue is empty," + "Consumer thread is waiting" + " for producer thread to put something in queue"); 
77       try { 
78        queue.wait(); 
79       } catch (Exception ex) { 
80        ex.printStackTrace(); 
81       } 
82      } 
83      System.out.println("Consuming value : " + queue.remove()); queue.notifyAll(); 
84     } 
85    } 
86   } 
87 } 

如何在 Java 中正確使用 wait, notify 和 notifyAll – 以生產者消費者模型爲例

爲了更好地理解這個程序,我建議你在debug模式裏跑這個程序。一旦你在debug模式下啓動程序,它會中止在PRODUCER或者 CONSUMER線程上,取決於哪一個線程佔據了CPU。由於兩個線程都有wait()的條件,它們必定會中止,而後你就能夠跑這個程序而後看發生什麼了 (頗有可能它就會輸出咱們以上展現的內容)。你也可使用Eclipse裏的Step into和Step over按鈕來更好地理解多線程間發生的事情。

本文重點:

1. 你可使用wait和notify函數來實現線程間通訊。你能夠用它們來實現多線程(>3)之間的通訊。

2. 永遠在synchronized的函數或對象裏使用wait、notify和notifyAll,否則Java虛擬機會生成 IllegalMonitorStateException。

3. 永遠在while循環裏而不是if語句下使用wait。這樣,循環會在線程睡眠先後都檢查wait的條件,並在條件實際上並未改變的狀況下處理喚醒通知。

4. 永遠在多線程間共享的對象(在生產者消費者模型裏即緩衝區隊列)上使用wait。

5. 基於前文說起的理由,更傾向用 notifyAll(),而不是 notify()。

如何在 Java 中正確使用 wait, notify 和 notifyAll – 以生產者消費者模型爲例

這是關於Java裏如何使用wait, notify和notifyAll的全部重點啦。你應該只在你知道本身要作什麼的狀況下使用這些函數,否則Java裏還有不少其它的用來解決同步問題的方 案。例如,若是你想使用生產者消費者模型的話,你也可使用BlockingQueue,它會幫你處理全部的線程安全問題和流程控制。若是你想要某一個線 程等待另外一個線程作出反饋再繼續運行,你也可使用CycliBarrier或者CountDownLatch。若是你只是想保護某一個資源的話,你也可 以使用Semaphore。

相關文章
相關標籤/搜索