生产者消费者问题之虚假唤醒

一、生产者消费者情景

在并发问题中,考虑这么一种生产者消费者情景:

设计A、B两个线程和一个参数count,如果count == 0,A线程进行生产,即对count + 1;如果count == 1,B线程进行消费,即对count - 1。

代码如下:

public class ProducerConsumer {

    public static void main(String[] args) {
        Data data = new Data();
        //A线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.increment();
            }},"A").start();
        //B线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }},"B").start();
    }


}

class Data{
    private int count = 0;

    public synchronized void increment(){
    	//判断等待
        if(count != 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //业务
        count++;
        System.out.println(Thread.currentThread().getName() + "->" + count);
        //唤醒
        this.notify();
    }
    public synchronized void decrement(){
    	//判断等待
        if(count == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //业务
        count--;
        System.out.println(Thread.currentThread().getName() + "->" + count);
        //唤醒
        this.notify();
    }
}

执行结果正常:

A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0

Process finished with exit code 0

在只有两个线程的情况下,该程序能正常执行,但是如果增加几个线程,变成四个线程,结果会怎样?

public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.increment();
            }},"A").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }},"B").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.increment();
            }},"C").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }},"D").start();
    }

//其余代码不变

执行结果就变得不稳定了,有时候能正常输入,有时候输出异常值:

A->1
B->0
A->1
C->2
A->3
C->4
A->5
C->6
A->7
C->8
B->7
B->6
B->5
B->4
C->5
D->4
D->3
D->2
D->1
D->0
    
Process finished with exit code 0

二、虚假唤醒

以上代码就涉及了线程虚假唤醒的问题

那什么是虚假唤醒?

wikipedia的解释为:

This means that when you wait on a condition variable, the wait may (occasionally) return when no thread specifically broadcast or signaled that condition variable. Spurious wakeups may sound strange, but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations. The race conditions that cause spurious wakeups should be considered rare.

可以理解为,当一个notify被调用时,导致多个线程被唤醒,正常情况下应该是一个notify唤醒一个线程。

虚假唤醒是操作系统底层的问题,也是一个小几率问题。


三、解决方案

if 判断改成 while 循环判断:

public class ProducerConsumer {

    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.increment();
            }},"A").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }},"B").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.increment();
            }},"C").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }},"D").start();
    }


}

class Data{
    private int count = 0;

    public synchronized void increment(){
        //if改成while循环
        while(count != 0){
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //业务
        count++;
        System.out.println(Thread.currentThread().getName() + "->" + count);
        //唤醒
        this.notify();
    }
    public synchronized void decrement(){
        //if改成while循环
        while(count == 0){
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //业务
        count--;
        System.out.println(Thread.currentThread().getName() + "->" + count);
        //唤醒
        this.notify();
    }
}

如果采用if的形式的话,判断一次就直接往下跑了,但是如果采用while,它会多次判断你的锁条件标志有没有被修改,就会再次进入wait状态中。

问题解决:

A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
C->1
B->0
A->1
D->0
C->1
D->0
C->1
D->0
C->1
D->0
C->1
D->0

Process finished with exit code 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值