JAVA中使用synchronized关键字实现 生产者和消费者 (虚假唤醒)

前置知识

wait()方法:让当前线程处于等待状态,会释放当前的锁对象。其他线程可以重新拿到锁。
notifyAll()方法:唤醒所有线程。
synchronized :当前线程拿到锁后,其他现在就无法再去获取锁,会进行监视等待。
这个前置知识必须要先知道,后面也会演示给同学们看

for example:

public class Test {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        new Thread(() -> {
            try {
                test2.add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        
        new Thread(() -> {
            try {
                test2.add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

class Test2 {
    private int i = 0;

    public void add() throws InterruptedException {
        synchronized (this) {
            i++;
            System.out.println(i);
            //this.wait();
            while (true) {
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述

  • 很明显程序暂未停止,thread-0一直处于RUNNING状态运行状态中,thread-0一直在while死循环中。
    thread-1一直处于监视状态,等待这 thread-0释放锁对象。

当我们将wait()方法给加上后如图所示:

在这里插入图片描述

在这里插入图片描述
很明显可以看到threa-1也成功拿到了这个锁对象,且执行了业务代码,然后执行wait方法,然后线程的状态也处于了wait状态。

实现生产者和消费者

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

        Date date = new Date();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {

                try {
                    date.add();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {

                try {
                    date.down();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();
    }

}

class Date {

    private int num = 0;

    public synchronized void add() throws InterruptedException {
        if (num != 0) {

            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        this.notifyAll();
    }

    public synchronized void down() throws InterruptedException {
        if (num == 0) {

            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "=>" + num);

        this.notifyAll();
    }

}

控制台输出如下所示:在这里插入图片描述

这样就是简单的实现了一个生产者和消费者,thread-0生产一个消息,tread-1消费一个消息。

但是这个地方是有一个坑的。
bug: 加入我们现在有两个生产者和两个消费者就会出现问题。

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

        Date date = new Date();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {

                try {
                    date.add();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {

                try {
                    date.down();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {

                try {
                    date.add();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {

                try {
                    date.down();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();
    }

}

class Date {
    private int num = 0;

    public synchronized void add() throws InterruptedException {
        if (num != 0) {

            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        this.notifyAll();
    }

    public synchronized void down() throws InterruptedException {
        if (num == 0) {

            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "=>" + num);

        this.notifyAll();
    }

}

现在是两个生产者和两个消费者,控制台输入如下:
在这里插入图片描述

  • 很明显这个时候已经是错的,理论上说控制台上只应该出现1和0,因为生产一个消息,就会立马被消费掉。

什么原因导致的呢 虚假唤醒

  1. 当一个线程调用add方法执行++后会调用notifyAll唤醒其他的线程,然后自己进入等待状态
  2. 由于自己调用wait方法进入了等待状态就会释放当前锁对象,所以其他调用add的线程也会成功拿到锁,满足if条件后进入wait等待状态中去。
  3. 其他线程执行–后唤醒所有的线程,此时会唤醒add中的两个等待的线程,然后同时去进行++的操作。

如何解决

  • 导致问题的关键原因就是if语句只会判断一次,当两个线程被唤醒后会立即往下执行语句。
  • 所以我们需要加if语句换成while语句即可,这样当两个线程被唤醒后只有其中一个线程会拿到锁对象。
  • 然后进行while判断发现已经num==0不符合条件跳出循环往下执行++,执行完后释放锁对象。
  • 第二个线程再拿到锁对象,再进行判断的时候已经满足条件,继续wait等待。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值