Java线程虚假唤醒是什么、如何避免?

一、什么是虚假唤醒?

多线程环境下,有多个线程执行了wait()方法,需要其他线程执行notify()或者notifyAll()方法去唤醒它们,假如多个线程都被唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功;对于不应该被唤醒的线程而言,便是虚假唤醒。
比如:仓库有货了才能出库,突然仓库入库了一个货品;这时所有的线程(货车)都被唤醒,来执行出库操作;实际上只有一个线程(货车)能执行出库操作,其他线程都是虚假唤醒。

下图为JAVA8中对wait()方法虚假唤醒的介绍:
在这里插入图片描述
意思是:虚假唤醒是可能发生的,我们应该在while循环中使用wait()方法。

二、怎么产生虚假唤醒?

假设当前有4个线程分别为ProducerA,ProducerB,ConsumerC,ConsumerD,其中ProducerA、ProducerB线程是生产者,ConsumerC、ConsumerD线程是消费者。当ProducerA或ProducerB线程生产了一个数据后会通知消费者去消费,ConsumerC或ConsumerD消费掉该条数据后会通知生产者去生产,数据的大小为1。也就是说正常情况下,数据只会有0和1两种值,0表示生产者该生产数据了,1表示消费者该消费数据了。

public class SpuriousWakeup2 {

    public static void main(String[] args) {
        WareHouse wareHouse = new WareHouse();
        Producer producer = new Producer(wareHouse);
        Customer customer = new Customer(wareHouse);

        new Thread(producer, "ProducerA").start();
        new Thread(producer, "ProducerB").start();

        new Thread(customer, "ConsumerC").start();
        new Thread(customer, "ConsumerD").start();
    }
}

// 仓库
class WareHouse {
    private volatile int product = 0;

    // 入库
    public synchronized void purchase() {
        // 库存已满,仓库最多容纳1个货品
        if (product > 0) {
            System.out.println(Thread.currentThread().getName() + ": " + "已满!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                // ignore exception
            }
        }
        ++product;
        // 该线程从while中出来的时候,已满足条件
        System.out.println(Thread.currentThread().getName() + ": " + "-------------入库成功,余货:" + product);
        this.notifyAll();
    }

    // 出库
    public synchronized void outbound() {
        if (product <= 0) {
            System.out.println(Thread.currentThread().getName() + ": " + "库存不足,无法出库");
            try {
                this.wait();
            } catch (InterruptedException e) {
                // ignore exception
            }
        }
        --product;
        System.out.println(Thread.currentThread().getName() + ":出库成功,余货:" + product);
        this.notifyAll();
    }
}

// 生产者
class Producer implements Runnable {
    private WareHouse wareHouse;

    public Producer(WareHouse wareHouse) {
        this.wareHouse = wareHouse;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
            wareHouse.purchase();
        }
    }
}

// 消费者
class Customer implements Runnable {
    private WareHouse wareHouse;

    public Customer(WareHouse wareHouse) {
        this.wareHouse = wareHouse;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            wareHouse.outbound();
        }
    }
}

运行结果:
在这里插入图片描述
我丢,看这数据就离谱,怎么还有-1、-9的?
出现这种问题的场景如是:当product为1的时候,说明线程ProducerA或者ProducerB已经生产了一条数据,其会执行notifyAll()唤醒线程ConsumerC和ConsumerD,然而被唤醒的两个Consumer线程,并没有再次去判断product的状态,便执行了–product逻辑,导致两个Comsumer同时执行了outbound()方法。也就出现上面所示的运行结果。
问题的关键点在于:程序仅使用if对product做了一次判断,我们应该使用while循环去判断。即wait()要在while循环中

1、为什么 if会出现虚假唤醒?

  • 因为if只会执行一次,执行完会接着向下执行if(){}后边的逻辑;
  • 而while不会,直到条件满足才会向下执行while(){}后边的逻辑。

三、如何避免虚假唤醒

使用while循环去循环判断一个条件,而不是使用if只判断一次条件;即wait()要在while循环中。

修改后的代码:

public class SpuriousWakeup2 {

    public static void main(String[] args) {
        WareHouse wareHouse = new WareHouse();
        Producer producer = new Producer(wareHouse);
        Customer customer = new Customer(wareHouse);

        new Thread(producer, "ProducerA").start();
        new Thread(producer, "ProducerB").start();

        new Thread(customer, "ConsumerC").start();
        new Thread(customer, "ConsumerD").start();
    }
}

// 仓库
class WareHouse {
    private volatile int product = 0;

    // 入库
    public synchronized void purchase() {
        // 库存已满,仓库最多容纳1个货品
        while (product > 0) {
            System.out.println(Thread.currentThread().getName() + ": " + "已满!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                // ignore exception
            }
        }
        ++product;
        // 该线程从while中出来的时候,已满足条件
        System.out.println(Thread.currentThread().getName() + ": " + "-------------入库成功,余货:" + product);
        this.notifyAll();
    }

    // 出库
    public synchronized void outbound() {
        while (product <= 0) {
            System.out.println(Thread.currentThread().getName() + ": " + "库存不足,无法出库");
            try {
                this.wait();
            } catch (InterruptedException e) {
                // ignore exception
            }
        }
        --product;
        System.out.println(Thread.currentThread().getName() + ":出库成功,余货:" + product);
        this.notifyAll();
    }
}

// 生产者
class Producer implements Runnable {
    private WareHouse wareHouse;

    public Producer(WareHouse wareHouse) {
        this.wareHouse = wareHouse;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
            wareHouse.purchase();
        }
    }
}

// 消费者
class Customer implements Runnable {
    private WareHouse wareHouse;

    public Customer(WareHouse wareHouse) {
        this.wareHouse = wareHouse;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            wareHouse.outbound();
        }
    }
}

运行结果:
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃秃爱健身

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值