生产者消费者和虚假唤醒

1 定义

虚假唤醒,即spurious wakeups。wait需要在while循环内使用,原因就是因为存在虚假唤醒。

2 Monitor

还是放上这个神图来复习下线程间通信

  • 线程在竞争锁失败的情况下会放到Entry Set中,图中2表示线程可以获取锁
  • 获取到锁的线程可以调用wait方法,让线程阻塞,此时线程被放到了Wait Set中,如图中3所示;Wait Set中的线程在时间到或者被notify后可以竞争锁,如图中4所示
  • Wait Set中的线程在获取到锁后才可以继续执行。
  • notify会唤醒Wait Set中的一个线程来竞争锁,notifyAll会唤醒Wait Set中全部的线程,但是只有一个线程能获取到锁,每个线程会依次去获取锁,运行一次。

3 虚假唤醒

我们以生产者消费者问题来举例几种情况,假设在wait在if中而不是在while中

1)情况1 稍微复杂点

  1. 有一个生产者p,两个消费者c1、c2,一个队列queue
  2. c1先执行,由于queue中为0,所以c1调用wait线程阻塞,线程放到了Wait Set中
  3. p生产了一个消费,放到queue中
  4. 在p调用notify之前,c2开始执行,需要竞争queue的锁,所以c2在Entry Set等待竞争锁
  5. p生产完成,调用notify,c1收到被唤醒后,从Wait Set竞争锁,注意此时c2也在竞争锁。
  6. c2从Entry Set先竞争到锁,然后消费了queue中的消息,此时queue大小为0
  7. c2执行完后,释放锁,此时c1竞争到锁,从queue中消费消息,由于queue目前大小为0,所以从queue为0的队列中访问是非法的。

2)情况2 稍微简单点

  1. 有一个生产者p,两个消费者c1、c2,一个队列queue
  2. c1、c2先启动,由于queue是空,所以分别调用wait,c1、c2都进入Wait Set
  3. 之后p生产了一个消息到queue中,然后调用notifyall,c1和c2都被唤醒。
  4. c1竞争到锁,消费一个消息,queue大小为0,完成后释放锁
  5. c2竞争到锁,消费一条消息,queue大小是-1,抛出异常。

3)情况3

  1. 假设有两种情况会引起消费者阻塞
  2. c1是由于条件1,调用了wait;c2是由于条件2调用了wait;
  3. p生产了一条消息,然后满足了条件1,调用notify,却唤醒了c2
  4. 由于是使用的if,c2没有再判断是不是条件2被满足了,所以就直接获取到锁,造成错误

4)情况4

  1. wait可能被interrupt等不被唤醒就继续执行

4 举例

我们以情况2来举例

public class ProducerConsumer {
    public static void main(String[] args) throws Exception{
        new ProducerConsumer().test();
    }

    public void test() throws InterruptedException {
        Queue<Integer> queue = new LinkedList<>();
        Producer producer = new Producer(queue);
        Consumer consumer = new Consumer(queue, 0);
        Consumer consumer1 = new Consumer(queue, 1);

        ExecutorService executor = Executors.newFixedThreadPool(6);
        executor.submit(consumer);
        executor.submit(consumer1);
        Thread.sleep(5000);
        executor.submit(producer);

    }

    class Producer implements Runnable {
        private Queue<Integer> queue;

        public Producer(Queue<Integer> queue) {
            this.queue = queue;
        }
        @Override
        public void run(){
            try {
                synchronized (queue) {
                    Integer time = new Random().nextInt(100);
                    queue.add(time);
                    System.out.println("producer notifyall");
                    queue.notifyAll();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    class Consumer implements Runnable {
        private Queue<Integer> queue;
        private int index;

        public Consumer(Queue<Integer> queue, int i) {
            this.queue = queue;
            this.index = i;
        }

        @Override
        public void run() {
            try {
                Thread.currentThread().setName("consumerThread_" + index);
                while (true) {
                    synchronized (queue) {
                        if (queue.size() <= 0) {
                            try {
                                System.out.println("consumer wait:" + Thread.currentThread().getName());
                                queue.wait();
                                System.out.println("consumer wait2:" + Thread.currentThread().getName());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("gogogo:" + Thread.currentThread().getName());
                        Integer time = new Random().nextInt(100);
                        try {
                            Thread.sleep(time);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("consumer remove: " + queue.remove() + ": " + Thread.currentThread().getName());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

运行结果

consumer wait:consumerThread_0
consumer wait:consumerThread_1
producer notifyall
consumer wait2:consumerThread_1
gogogo:consumerThread_1
consumer remove: 99: consumerThread_1
consumer wait:consumerThread_1
consumer wait2:consumerThread_0
gogogo:consumerThread_0
java.util.NoSuchElementException
    at java.util.LinkedList.removeFirst(LinkedList.java:270)
    at java.util.LinkedList.remove(LinkedList.java:685)
    at com.hxy.ProducerConsumer$Consumer.run(ProducerConsumer.java:85)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

上述代码中,两个消费者先启动,由于queue.size 小于等于0 ,所以两个消费者都调用wait,放到了Wait Set中;5s后生产者往队列里发送了一条消息,然后调用notifyAll,两个消费者都被唤醒,由于没有再判断是否满足条件,所以分别获取锁去消费,造成第二个消费者抛出NoSuchElementException异常。

将上述代码中消费者的if判断修改为while便会正常,运行结果如下:

consumer wait:consumerThread_0
consumer wait:consumerThread_1
producer notifyall
consumer wait2:consumerThread_1
gogogo:consumerThread_1
consumer remove: 53: consumerThread_1
consumer wait:consumerThread_1
consumer wait2:consumerThread_0
consumer wait:consumerThread_0

consumerThread_0竞争到锁后,会从while判断queue.size大小,由于queue大小为0,所以继续wait。

另外,可能会想到,在while使用wait,对于情况3这种情况,如果调用notifyall,会唤醒其他不相关的线程,而这些线程需要重新判断,然后再调用wait,这显然是一种资源浪费。针对这种情况,我们可以使用Condition,只唤醒相关的线程。

5 生产者消费者

下面附上正确的消费者生产者

public class ProducerConsumer {
    public static void main(String[] args) throws Exception{
        new ProducerConsumer().test();
    }

    public void test() throws InterruptedException {
        Queue<Integer> queue = new LinkedList<>();
        Producer producer = new Producer(queue);
        Consumer consumer = new Consumer(queue, 0);
        Consumer consumer1 = new Consumer(queue, 1);

        ExecutorService executor = Executors.newFixedThreadPool(6);
        executor.submit(consumer);
        executor.submit(consumer1);
        executor.submit(producer);

    }

    class Producer implements Runnable {
        private Queue<Integer> queue;

        public Producer(Queue<Integer> queue) {
            this.queue = queue;
        }
        @Override
        public void run(){
            while (true) {
                try {
                    synchronized (queue) {
                        while (queue.size() >= 10) { // 防止虚假唤醒
                            queue.wait();
                        }
                        Integer time = new Random().nextInt(100);

                        Thread.sleep(time);

                        System.out.println("producer add:" + time);
                        queue.add(time);
                        queue.notifyAll();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class Consumer implements Runnable {
        private Queue<Integer> queue;
        private int index;

        public Consumer(Queue<Integer> queue, int i) {
            this.queue = queue;
            this.index = i;
        }

        @Override
        public void run() {
            try {
                Thread.currentThread().setName("consumerThread_" + index);
                while (true) {
                    synchronized (queue) {
                        while (queue.size() <= 0) { // 防止虚假唤醒
                            queue.wait();
                        }
                        Integer time = new Random().nextInt(100);

                        Thread.sleep(time);

                        System.out.println("consumer remove: " + queue.remove() + ": " + Thread.currentThread().getName());
                        queue.notifyAll(); // 也有可能唤醒另一个consumer中的queue.wait
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

原文地址:https://www.cnblogs.com/set-cookie/p/8728907.html

时间: 2024-08-24 07:46:11

生产者消费者和虚假唤醒的相关文章

java多线程 生产者消费者案例-虚假唤醒

package com.java.juc; public class TestProductAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Produce produce = new Produce(clerk); Consumer consumer = new Consumer(clerk); new Thread(produce, "线程A").start(); ne

生产者与消费者案例-虚假唤醒

以下是一个案例,有一个店员,负责进货和卖货.进货生产,卖货消费. 当商品超过10件,生产等待,消费继续,当少于0件,消费等待,消费继续. 正常代码如下: package com.atguigu.juc; /* * 生产者和消费者案例 */ public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pro = new Pr

(三)juc高级特性——虚假唤醒 / Condition / 按序交替 / ReadWriteLock / 线程八锁

8. 生产者消费者案例-虚假唤醒 参考下面生产者消费者案例: /* * 生产者和消费者案例 */ public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pro = new Productor(clerk); Consumer cus = new Consumer(clerk); new Thread(pro, "生产

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

首先引入下面这段生产者和消费者的程序,店员类作为生产产品和消费产品的中介,其中的数据product为共享数据,产品最多只能囤积5个,当产品达到5个还在生产时,就会提示"产品已满!",类似地,如果产品只有0个了还在消费,会提示"缺货!": 1 package concurrent; 2 3 //店员类 4 class Clerk { 5 private int product = 0; 6 7 // 进货 8 public synchronized void get(

Java多线程虚假唤醒问题(生产者和消费者关系)

何为虚假唤醒: 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功:比如买货:如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁: 避免虚假唤醒: 避免虚假唤醒的示例:这里使用了 Lambda 表达式 package com.jia.pc; public class A { public static void main(String[] args) { Data data = new

java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

 *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时候才能消费,仓空则等待. *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产. *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费. 下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费. 生产一个资源,就得消费一个资源. 代码如下: pub

java多线程中的生产者与消费者之等待唤醒机制@Version2.0

二.生产者消费者模式的学生类成员变量生产与消费demo, @Version2.0 在学生类中添加同步方法:synchronized get()消费者,synchronized set()生产者 最终版的代码中: 把student的成员变量给私有化了, 把设置和获取的功能给封装成了功能,并加了同步, 设置或者获取的线程里面只需要调用方法即可. 1.等待唤醒:    Object类中提供了三个方法:    wait():等待    notify():唤醒单个线程    notifyAll():唤醒所

java多线程中的生产者与消费者之等待唤醒机制@Version1.0

一.生产者消费者模式的学生类成员变量生产与消费demo,第一版1.等待唤醒:    Object类中提供了三个方法:    wait():等待    notify():唤醒单个线程    notifyAll():唤醒所有线程2.为什么这些方法不定义在Thread类中呢?  这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象.  所以,这些方法必须定义在Object类中.3.当我们在使用多线程的时候有的时候需要,一条线程产生一个数据,另一条线程接着消费一个数据,一边生产一边消费,

Android-Java多线程通讯(生产者 消费者)&amp;10条线程对-等待唤醒/机制的管理

上一篇博客 Android-Java多线程通讯(生产者 消费者)&等待唤醒机制 是两条线程(Thread-0 / Thread-1) 在被CPU随机切换执行: 而今天这篇博客是,在上一篇博客Android-Java多线程通讯(生产者 消费者)&等待唤醒机制 的基础上,扩大规模增加10条线程去执行 生产者 消费者: 注意:?? 上一篇博客是两条线程在执行(生产者 消费者)例如:当Thread-0 锁.wait(); 等待 冻结后,  Thread-1 锁.notify(); 唤醒的一定是 T