JAVA线程虚假唤醒

线程虚假唤醒问题描述

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

JAVA线程虚假唤醒_第1张图片

​ 在多线程的情况下,当多个线程执行了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();
    }
}

程序结果

JAVA线程虚假唤醒_第2张图片

结果分析

​ 可以看到上述结果出现了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线程虚假唤醒)