今天正好碰到這個問題,也疑惑了很久。看了一圈知乎上的答案,感受沒說到根上。因此本身又好好Google了一下,終於找到了讓本身信服的解釋。html
先說兩個概念:鎖池和等待池java
Reference: java中的鎖池和等待池
而後再來講notify和notifyAll的區別程序員
Reference: 線程間協做:wait、notify、notifyAll
綜上,所謂喚醒線程,另外一種解釋能夠說是將線程由等待池移動到鎖池,notifyAll調用後,會將所有線程由等待池移到鎖池,而後參與鎖的競爭,競爭成功則繼續執行,若是不成功則留在鎖池等待鎖被釋放後再次參與競爭。而notify只會喚醒一個線程。面試
有了這些理論基礎,後面的notify可能會致使死鎖,而notifyAll則不會的例子也就好解釋了安全
仍是直接上代碼:多線程
public class WaitAndNotify {
併發
public static void main(String[] args) {
dom
Object co = new Object();
ide
System.out.println(co);
函數
for (int i = 0; i < 5; i++) {
MyThread t = new MyThread("Thread" + i, co);
t.start();
}
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("-----Main Thread notify-----");
synchronized (co) {
co.notify();
}
TimeUnit.SECONDS.sleep(2);
System.out.println("Main Thread is end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class MyThread extends Thread {
private String name;
private Object co;
public MyThread(String name, Object o) {
this.name = name;
this.co = o;
}
public void run() {
System.out.println(name + " is waiting.");
try {
synchronized (co) {
co.wait();
}
System.out.println(name + " has been notified.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運行結果:
java.lang.Object@1540e19d
Thread1 is waiting.
Thread2 is waiting.
Thread0 is waiting.
Thread3 is waiting.
Thread4 is waiting.
-----Main Thread notify-----
Thread1 has been notified.
Main Thread is end.
將其中的那個notify換成notifyAll,運行結果:
Thread0 is waiting.
Thread1 is waiting.
Thread2 is waiting.
Thread3 is waiting.
Thread4 is waiting.
-----Main Thread notifyAll-----
Thread4 has been notified.
Thread2 has been notified.
Thread1 has been notified.
Thread3 has been notified.
Thread0 has been notified.
Main Thread is end.
運行環境jdk8,結論:
notify喚醒一個等待的線程;notifyAll喚醒全部等待的線程。
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和notify的概念很基礎,它們也都是Object類的函數,但用它們來寫代碼卻並不簡單。若是你在面試中讓應聘者來手寫代碼,用wait和notify解決生產者消費者問題,我幾乎能夠確定他們中的大多數都會無所適從或者犯下一些錯誤,例如在錯誤的地方使用 synchronized 關鍵詞,沒有對正確的對象使用wait,或者沒有遵循規範的代碼方法。說實話,這個問題對於不常使用它們的程序員來講確實使人感受比較頭疼。
第一個問題就是,咱們怎麼在代碼裏使用wait()呢?由於wait()並非Thread類下的函數,咱們並不能使用Thread.call()。事實上不少Java程序員都喜歡這麼寫,由於它們習慣了使用Thread.sleep(),因此他們會試圖使用wait() 來達成相同的目的,但很快他們就會發現這並不能順利解決問題。正確的方法是對在多線程間共享的那個Object來使用wait。在生產者消費者問題中,這個共享的Object就是那個緩衝區隊列。
第二個問題是,既然咱們應該在synchronized的函數或是對象裏調用wait,那哪一個對象應該被synchronized呢?答案是,那個你但願上鎖的對象就應該被synchronized,即那個在多個線程間被共享的對象。在生產者消費者問題中,應該被synchronized的就是那個緩衝區隊列。(我以爲這裏是英文原文有問題……原本那個句末就不該該是問號否則不太通……)
如今你知道wait應該永遠在被synchronized的背景下和那個被多線程共享的對象上調用,下一個必定要記住的問題就是,你應該永遠在while循環,而不是if語句中調用wait。由於線程是在某些條件下等待的——在咱們的例子裏,即「若是緩衝區隊列是滿的話,那麼生產者線程應該等待」,你可能直覺就會寫一個if語句。但if語句存在一些微妙的小問題,致使即便條件沒被知足,你的線程你也有可能被錯誤地喚醒。因此若是你不在線程被喚醒後再次使用while循環檢查喚醒條件是否被知足,你的程序就有可能會出錯——例如在緩衝區爲滿的時候生產者繼續生成數據,或者緩衝區爲空的時候消費者開始小號數據。因此記住,永遠在while循環而不是if語句中使用wait!我會推薦閱讀《Effective Java》,這是關於如何正確使用wait和notify的最好的參考資料。
基於以上認知,下面這個是使用wait和notify函數的規範代碼模板:
1 2 3 4 5 6 7 8 |
|
就像我以前說的同樣,在while循環裏使用wait的目的,是在線程被喚醒的先後都持續檢查條件是否被知足。若是條件並未改變,wait被調用以前notify的喚醒通知就來了,那麼這個線程並不能保證被喚醒,有可能會致使死鎖問題。
下面咱們提供一個使用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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 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。