在 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的就是那個 緩衝區隊列。(我以爲這裏是英文原文有問題……原本那個句末就不該該是問號否則不太通……)函數
永遠在循環(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 }
爲了更好地理解這個程序,我建議你在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裏還有不少其它的用來解決同步問題的方 案。例如,若是你想使用生產者消費者模型的話,你也可使用BlockingQueue,它會幫你處理全部的線程安全問題和流程控制。若是你想要某一個線 程等待另外一個線程作出反饋再繼續運行,你也可使用CycliBarrier或者CountDownLatch。若是你只是想保護某一個資源的話,你也可 以使用Semaphore。