java中的notify和notifyAll有什麼區別?

今天正好碰到這個問題,也疑惑了很久。看了一圈知乎上的答案,感受沒說到根上。因此本身又好好Google了一下,終於找到了讓本身信服的解釋。html

先說兩個概念:鎖池和等待池java

  • 鎖池:假設線程A已經擁有了某個對象(注意:不是類)的鎖,而其它的線程想要調用這個對象的某個synchronized方法(或者synchronized塊),因爲這些線程在進入對象的synchronized方法以前必須先得到該對象的鎖的擁有權,可是該對象的鎖目前正被線程A擁有,因此這些線程就進入了該對象的鎖池中。
  • 等待池:假設一個線程A調用了某個對象的wait()方法,線程A就會釋放該對象的鎖後,進入到了該對象的等待池中
Reference: java中的鎖池和等待池

而後再來講notify和notifyAll的區別程序員

  • 若是線程調用了對象的 wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖
  • 當有線程調用了對象的 notifyAll()方法(喚醒全部 wait 線程)或 notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調用了notify後只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內的全部線程移動到鎖池中,等待鎖競爭
  • 優先級高的線程競爭到對象鎖的機率大,倘若某線程沒有競爭到該對象鎖,它還會留在鎖池中,惟有線程再次調用 wait()方法,它纔會從新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了 synchronized 代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。
Reference: 線程間協做:wait、notify、notifyAll

綜上,所謂喚醒線程,另外一種解釋能夠說是將線程由等待池移動到鎖池,notifyAll調用後,會將所有線程由等待池移到鎖池,而後參與鎖的競爭,競爭成功則繼續執行,若是不成功則留在鎖池等待鎖被釋放後再次參與競爭。而notify只會喚醒一個線程。面試

有了這些理論基礎,後面的notify可能會致使死鎖,而notifyAll則不會的例子也就好解釋了安全

仍是直接上代碼:多線程

 
  1. public class WaitAndNotify {併發

  2. public static void main(String[] args) {dom

  3. Object co = new Object();ide

  4. System.out.println(co);函數

  5.  
  6. for (int i = 0; i < 5; i++) {

  7. MyThread t = new MyThread("Thread" + i, co);

  8. t.start();

  9. }

  10.  
  11. try {

  12. TimeUnit.SECONDS.sleep(2);

  13. System.out.println("-----Main Thread notify-----");

  14. synchronized (co) {

  15. co.notify();

  16. }

  17.  
  18. TimeUnit.SECONDS.sleep(2);

  19. System.out.println("Main Thread is end.");

  20.  
  21. } catch (InterruptedException e) {

  22. e.printStackTrace();

  23. }

  24. }

  25.  
  26. static class MyThread extends Thread {

  27. private String name;

  28. private Object co;

  29.  
  30. public MyThread(String name, Object o) {

  31. this.name = name;

  32. this.co = o;

  33. }

  34.  
  35. @Override

  36. public void run() {

  37. System.out.println(name + " is waiting.");

  38. try {

  39. synchronized (co) {

  40. co.wait();

  41. }

  42. System.out.println(name + " has been notified.");

  43. } catch (InterruptedException e) {

  44. e.printStackTrace();

  45. }

  46. }

  47. }

  48. }

  49.  
  50.  
  51. 運行結果:

  52. java.lang.Object@1540e19d

  53. Thread1 is waiting.

  54. Thread2 is waiting.

  55. Thread0 is waiting.

  56. Thread3 is waiting.

  57. Thread4 is waiting.

  58. -----Main Thread notify-----

  59. Thread1 has been notified.

  60. Main Thread is end.

  61.  
  62. 將其中的那個notify換成notifyAll,運行結果:

  63. Thread0 is waiting.

  64. Thread1 is waiting.

  65. Thread2 is waiting.

  66. Thread3 is waiting.

  67. Thread4 is waiting.

  68. -----Main Thread notifyAll-----

  69. Thread4 has been notified.

  70. Thread2 has been notified.

  71. Thread1 has been notified.

  72. Thread3 has been notified.

  73. Thread0 has been notified.

  74. Main Thread is end.

  75.  
  76. 運行環境jdk8,結論:

  77. notify喚醒一個等待的線程;notifyAll喚醒全部等待的線程。

如何在 Java 中正確使用 wait, 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

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

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

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

截圖7

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

如今你知道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

// The standard idiom for calling the wait method in Java

synchronized (sharedObject) {

    while (condition) {

    sharedObject.wait();

        // (Releases lock, and reacquires on wakeup)

    }

    // do action based upon condition e.g. take or put into queue

}

就像我以前說的同樣,在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

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

import java.util.LinkedList;

import java.util.Queue;

import java.util.Random;

/**

* Simple Java program to demonstrate How to use wait, notify and notifyAll()

* method in Java by solving producer consumer problem.

*

* @author Javin Paul

*/

public class ProducerConsumerInJava {

    public static void main(String args[]) {

        System.out.println("How to use wait and notify method in Java");

        System.out.println("Solving Producer Consumper Problem");

        Queue&lt;Integer&gt; buffer = new LinkedList&lt;&gt;();

        int maxSize = 10;

        Thread producer = new Producer(buffer, maxSize, "PRODUCER");

        Thread consumer = new Consumer(buffer, maxSize, "CONSUMER");

        producer.start(); consumer.start(); }

    }

    /**

    * Producer Thread will keep producing values for Consumer

    * to consumer. It will use wait() method when Queue is full

    * and use notify() method to send notification to Consumer

    * Thread.

    *

    * @author WINDOWS 8

    *

    */

    class Producer extends Thread

    { private Queue&lt;Integer&gt; queue;

        private int maxSize;

        public Producer(Queue&lt;Integer&gt; queue, int maxSize, String name){

            super(name); this.queue = queue; this.maxSize = maxSize;

        }

        @Override public void run()

        {

            while (true)

                {

                    synchronized (queue) {

                        while (queue.size() == maxSize) {

                            try {

                                System.out .println("Queue is full, " + "Producer thread waiting for " + "consumer to take something from queue");

                                queue.wait();

                            } catch (Exception ex) {

                                ex.printStackTrace(); }

                            }

                            Random random = new Random();

                            int i = random.nextInt();

                            System.out.println("Producing value : " + i); queue.add(i); queue.notifyAll();

                        }

                    }

                }

            }

    /**

    * Consumer Thread will consumer values form shared queue.

    * It will also use wait() method to wait if queue is

    * empty. It will also use notify method to send

    * notification to producer thread after consuming values

    * from queue.

    *

    * @author WINDOWS 8

    *

    */

    class Consumer extends Thread {

        private Queue&lt;Integer&gt; queue;

        private int maxSize;

        public Consumer(Queue&lt;Integer&gt; queue, int maxSize, String name){

            super(name);

            this.queue = queue;

            this.maxSize = maxSize;

        }

        @Override public void run() {

            while (true) {

                synchronized (queue) {

                    while (queue.isEmpty()) {

                        System.out.println("Queue is empty," + "Consumer thread is waiting" + " for producer thread to put something in queue");

                        try {

                            queue.wait();

                        } catch (Exception ex) {

                            ex.printStackTrace();

                        }

                    }

                    System.out.println("Consuming value : " + queue.remove()); queue.notifyAll();

                }

            }

        }

    }

截圖5

爲了更好地理解這個程序,我建議你在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()。

截圖6

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

相關文章
相關標籤/搜索