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。

相关文章
相关标签/搜索