必威体育Betway必威体育官网
当前位置:首页 > IT技术

java中的notify和notifyAll有什么区别?

时间:2019-08-16 22:43:15来源:IT技术作者:seo实验室小编阅读:72次「手机版」
 

notifyall

作者:知乎用户

链接:https://www.zhihu.com/question/37601861/answer/145545371

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

今天正好碰到这个问题,也疑惑了好久。看了一圈知乎上的答案,感觉没说到根上。所以自己又好好Google了一下,终于找到了让自己信服的解释。

先说两个概念:锁池和等待池

  • 锁池:假设线程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则不会的例子也就好解释了

作者:Alex Wang

链接:https://www.zhihu.com/question/37601861/answer/94679949

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

还是直接上代码:

public class WaitAndNotify {
public static void main(String[] args) {
        Object co = new Object();
        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;
        }

@Override
        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唤醒所有等待的线程。

如何在 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。

相关阅读

Android 解决 adapter.notifyDataSetChanged() 不起作

转载请注明出处:http://blog.csdn.net/like_program/article/details/52517119 使用 Listview 的时候,给 adapter 的数据源 List

超简单案例区分notify()和notifyAll()

notify()唤醒一个被挂起的线程,notifyAll唤醒所有被挂起的线程。代码思想:实现一个计数器,当只剩一个线程没有被挂起时,一次性唤醒所

Android ListView优化之局部刷新(更新)(非notifyDataSetC

转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53020164 【DylanAndroid的csdn博客】 在Android开

windows程序托盘图标以及消息提示--基本知识(NOTIFYICO

首先介绍一下基本知识: NOTIFYICONDATA结构体,语法如下: typedef struct _NOTIFYICONDATA { DWORD cbSize; HWND hWnd; UINT uID; UI

notifyDataSetChanged和notifyDataSetInvalidated区别

notifyDataSetChanged和notifyDataSetInvalidated区别以及观察者模式的使用 看Observer和Observable的时候,爬了下Listview的

分享到:

栏目导航

推荐阅读

热门阅读