码迷,mamicode.com
首页 > 编程语言 > 详细

JAVA线程虚假唤醒

时间:2020-07-04 22:26:00      阅读:66      评论:0      收藏:0      [点我收藏+]

标签:rgs   mamicode   strong   void   问题解决   incr   多线程   print   必须   

JAVA线程虚假唤醒

线程虚假唤醒问题描述

? 在JDK API文档中,关于Object类的wait()方法有这样一句话描述"线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待",如下图所示:

技术图片

? 在多线程的情况下,当多个线程执行了wait()方法后,需要其它线程执行notify()或者notifyAll()方法去唤醒,假如被阻塞的多个线程都被唤醒,但实际情况是被唤醒的线程中有一部分线程是不应该被唤醒的,那么对于这些不应该被唤醒的线程而言就是虚假唤醒。

问题复现

生产者与消费者问题

? 假设当前有4个线程分别为A,B,C,D,其中A,B线程是生产者,C,D线程是消费者,当A和B线程生产了一个数据后就去通知消费者去消费,C和D消费掉这一个数据后就通知生产者去生产,数据的大小为1。也就是说正常情况下,数据只会有0和1两种状态,0表示生产者该生产数据了,1表示消费者该消费数据了。

package producer_consumer;

public class PVTest {
    public static void main(String[] args) {
        Data data = new Data();
        //生产者线程A
        new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        //生产者线程B
        new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        //消费者线程C
        new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        //消费者线程D
        new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

//数据类
class Data {
    //表示数据个数
    private int number = 0;
    public synchronized void increment() throws InterruptedException {
        //关键点,这里应该使用while循环
        if (number != 0) {
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"生产了数据:"+number);
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        //关键点,这里应该使用while循环
        if (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"消费了数据:"+number);
        this.notifyAll();
    }
}

程序结果

技术图片

结果分析

? 可以看到上述结果出现了data为2的情况,不符合之前的预期,出现问题的场景是这样的:当data为1的时候,线程A和B先后获取锁去生产数据的时候会被阻塞住,然后消费者C或者D消费掉数据后去notifyAll()唤醒了线程A和B,被唤醒的A和B没有再次去判断data状态,就去执行后续增加数据的逻辑了,导致两个生产者都执行了increment(),最终data出现了2这种情况。也就是说线程A和B有一个是不应该被唤醒的却被唤醒了,出现这个问题的关键点在于程序中使用到了if判断,只判断了一次data的状态,应该使用while循环去判断

虚假唤醒问题解决

? 正如JDK API文档中所说在写程序时候应该用while去替代if,上述生产者和消费者代码中,将Data类中的increment()和decrement()方法中的if换为while即可避免线程虚假唤醒的问题。

JAVA线程虚假唤醒

标签:rgs   mamicode   strong   void   问题解决   incr   多线程   print   必须   

原文地址:https://www.cnblogs.com/un1que/p/13236905.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!