首页 > 技术文章 > 使用wait方法遇到的虚假唤醒问题

lanxinren 2021-04-20 16:51 原文

首先写一个生产者消费者案例

public class ProductAndConsumer03 {
    public static void main(String[] args) {

        Data data = new Data();
        //创建一个生产者
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        //创建一个消费者
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }

}

//这是一个缓冲类,生产和消费之间的仓库
class Data{
    //这是仓库的资源,生产者生产资源,消费者消费资源
    private int num = 0;

    // +1操作,利用关键字加锁
    public synchronized void increment() throws InterruptedException {
        //首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁
        if(num!=0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程 +1 执行完毕
        this.notifyAll();
    }

    //-1操作
    public synchronized void decrement() throws InterruptedException {
        //首先查看仓库中的资源(num),如果资源为0,就利用 wait 方法等待生产,释放锁
        if(num==0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}

结果(正确)

A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0

模仿上面的案例在增加一个生产者和消费者

package com.cn.Callable;
//虚假唤醒
public class ProductAndConsumer03 {
    public static void main(String[] args) {

        Data data = new Data();
        //创建一个生产者 A
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        //创建一个消费者 B
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        //创建一个生产者 C
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        //创建一个消费者 D
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }

}

//这是一个缓冲类,生产和消费之间的仓库
class Data{
    //这是仓库的资源,生产者生产资源,消费者消费资源
    private int num = 0;

    // +1操作,利用关键字加锁
    public synchronized void increment() throws InterruptedException {
        //首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁
        if(num!=0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程 +1 执行完毕
        this.notifyAll();
    }

    //-1操作
    public synchronized void decrement() throws InterruptedException {
        //首先查看仓库中的资源(num),如果资源为0,就利用 wait 方法等待生产,释放锁
        if(num==0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}

结果 我们发现出现了2这个不正确的结果(每次结果都可能不同)(if造成的,if只会判断一次)

A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0
C=>1 B=>0 A=>1 C=>2 D=>1 C=>2 D=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0

我们来看一下jdk帮助文档

image-20200810224826214

使用while替代if解决虚假唤醒问题

package com.cn.Callable;
//虚假唤醒
public class ProductAndConsumer03 {
    public static void main(String[] args) {

        Data data = new Data();
        //创建一个生产者 A
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        //创建一个消费者 B
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        //创建一个生产者 C
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        //创建一个消费者 D
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }

}

//这是一个缓冲类,生产和消费之间的仓库
class Data{
    //这是仓库的资源,生产者生产资源,消费者消费资源
    private int num = 0;

    // +1操作,利用关键字加锁
    public synchronized void increment() throws InterruptedException {
        //首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁
        while(num!=0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程 +1 执行完毕
        this.notifyAll();
    }

    //-1操作
    public synchronized void decrement() throws InterruptedException {
        //首先查看仓库中的资源(num),如果资源为0,就利用 wait 方法等待生产,释放锁
        while(num==0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}

结果正确

A=>1 B=>0 C=>1 B=>0 A=>1 B=>0 C=>1 B=>0 A=>1 B=>0 C=>1 B=>0 A=>1 B=>0 C=>1 B=>0 A=>1 B=>0
C=>1 B=>0 A=>1 D=>0 C=>1 D=>0 A=>1 D=>0 C=>1 D=>0 A=>1 D=>0 C=>1 D=>0 A=>1 D=>0 C=>1 D=>0 A=>1 D=>0 C=>1 D=>0

参考教程:狂神学Java

参考文章:https://blog.csdn.net/qq_36188127/article/details/110501841

推荐阅读