多线程交互虚假唤醒问题详解

多线程交互虚假唤醒问题详解

1.虚假唤醒产生的原因

我们以经典的生产者消费者问题来演示,看以下代码


class Test {

    private int number = 0;

    //生产者方法
    public synchronized void increment() throws InterruptedException {
        //判断,当number>0时线程等待,等待消费者消费
        if (number != 0) {
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        this.notifyAll();
    }
	//消费者方法
    public synchronized void decrement() throws InterruptedException {
        //判断,当number等于0时线程等待,等待生产者生产
        if (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        this.notifyAll();
    }

}
public class ThreadWaitNotifyDemo {
    public static void main(String[] args) {
        Test test = new Test();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(200);
                    test.increment();
                } catch (Exception e) {

                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(300);
                    test.decrement();
                } catch (Exception e) {
                }
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(400);
                    test.increment();
                } catch (Exception e) {
                }
            }
        }, "C").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(500);
                    test.decrement();
                } catch (Exception e) {
                }
            }
        }, "D").start();
    }
}

在以上代码中,我们开了四个线程,其中两个消费者线程,两个生产者线程。

在运行代码后,我们预期的结果应该是number一直维持在0和1之间,但是当我们

运行代码后出现以下结果

在这里插入图片描述

我们发现number出现2的情况,这意味着进行了两次的++操作。

2.原因分析

我们考虑这样一种情况。当某一个生产者线程进行生产之后,及代码中number++之后,此时number=1,当前线程进入等待,即阻塞状态。此时我们应该期望的是一个消费者线程进来进行消费,但是此时又有一个生产者线程进来,此时number还是1,当前线程继续等待,意味着此时有两个生产者线程在等待。此时外面只剩下消费者线程,随意进来一个线程进行消费之后,当前number=0;此时就会唤醒两个等待中的生产者线程。由于用if作为判断语句,在两个线程被唤醒之前已经经过判断,所以两个线程都会执行number++操作。从而导致number=2的情况。因此在进行多线程交互时,尤其是两个以上的线程交互时,做判断的时候不应该用if,而应该用while。

3.问题解决

经过上述原因分析,我们明白在做判断时应当用while。我们修改之前的代码,将if改为while:


class Test {

    private int number = 0;

    //生产者方法
    public synchronized void increment() throws InterruptedException {
        //判断,当number>0时线程等待,等待消费者消费
        while (number != 0) {
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        this.notifyAll();
    }
	//消费者方法
    public synchronized void decrement() throws InterruptedException {
        //判断,当number等于0时线程等待,等待生产者生产
        while (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        this.notifyAll();
    }

}
public class ThreadWaitNotifyDemo {
    public static void main(String[] args) {
        Test test = new Test();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(200);
                    test.increment();
                } catch (Exception e) {

                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(300);
                    test.decrement();
                } catch (Exception e) {
                }
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(400);
                    test.increment();
                } catch (Exception e) {
                }
            }
        }, "C").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(500);
                    test.decrement();
                } catch (Exception e) {
                }
            }
        }, "D").start();
    }
}

运行代码,得到以下结果

运行代码,得到以下结果

在这里插入图片描述

我们发现没有2出现,说明问题解决。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值