最近B站学习狂神的JUC并发编程时,听到了虚假唤醒这个词,虽然狂神进行了代码的演示,但我还是不太理解为什么使用if判断包装wait方法会出现虚假唤醒,查找了网上很多大佬的博客终于理解了,这里分享一下虚假唤醒产生的原因。

什么是虚假唤醒?

当一定的条件触发时会唤醒很多在阻塞态的线程,但只有部分的线程唤醒是有用的,其余线程的唤醒是多余的。
比如说卖货,如果本来没有货物,突然进了一件货物,这时所有的顾客都被通知了,但是只能一个人买,所以其他人都是无用的通知。

虚假唤醒演示

public class test {public static void main(String[] args) {Product product = new Product();new Thread(() -> {for (int i = 0; i < 10; i++) {try {product.push();} catch (InterruptedException e) {e.printStackTrace();}}}, "生产者A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {product.pop();} catch (InterruptedException e) {e.printStackTrace();}}}, "消费者A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {product.push();} catch (InterruptedException e) {e.printStackTrace();}}}, "生产者B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {product.pop();} catch (InterruptedException e) {e.printStackTrace();}}}, "消费者B").start();}
}class Product {private int product = 0;public synchronized void push() throws InterruptedException {// System.out.println(Thread.currentThread().getName() + "进入push方法");if (product > 0) {this.wait();}product++;System.out.println(Thread.currentThread().getName() + "添加产品,剩余" + product + "件产品");this.notifyAll();}public synchronized void pop() throws InterruptedException {// System.out.println(Thread.currentThread().getName() + "进入pop方法");if (product == 0) {this.wait();}product--;System.out.println(Thread.currentThread().getName() + "使用产品,剩余" + product + "件产品");this.notifyAll();}
}

程序中定义了两个生产者和两个消费者,产品缓冲区的大小为1,一旦生产者生产了产品,消费者就要去消费而生产者不得再生产。
理论上应该出现的结果:

生产者A添加产品,剩余1件产品
消费者A使用产品,剩余0件产品
生产者A添加产品,剩余1件产品
消费者A使用产品,剩余0件产品
生产者B添加产品,剩余1件产品
消费者A使用产品,剩余0件产品
生产者A添加产品,剩余1件产品
...

程序实际运行结果为:

生产者A添加产品,剩余1件产品
消费者A使用产品,剩余0件产品
生产者B添加产品,剩余1件产品
生产者A添加产品,剩余2件产品
生产者B添加产品,剩余3件产品
消费者A使用产品,剩余2件产品
消费者A使用产品,剩余1件产品
...

可以看到程序并没有实现同步的需求。实际上出现的结果可能远不止如此,那为什么会出现这种情况呢?

为了让程序执行步骤更好理解,我在push和pop方法前加入输出语句:

public synchronized void push() throws InterruptedException {System.out.println(Thread.currentThread().getName() + "进入push方法");...
}public synchronized void pop() throws InterruptedException {System.out.println(Thread.currentThread().getName() + "进入pop方法");...
}

执行结果如下:

生产者A进入push方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
消费者A进入pop方法
消费者A使用产品,剩余0件产品
消费者A进入pop方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
生产者B进入push方法
消费者A使用产品,剩余0件产品
消费者A进入pop方法
生产者B添加产品,剩余1件产品
生产者B进入push方法
生产者A添加产品,剩余2件产品
生产者A进入push方法
生产者B添加产品,剩余3件产品
...

步骤分析:

  1. 生产者A先进入push方法,此时没有产品,条件判断不成立,生产产品,唤醒其他线程
if (product > 0){this.wait();
}
生产者A进入push方法
生产者A添加产品,剩余1件产品
  1. 生产者A继续进入push方法,但是此时已有一个产品,条件满足,进入阻塞队列并释放锁
生产者A进入push方法
  1. 消费者A进入pop方法,此时已有产品,条件不满足,使用一个产品并唤醒其他线程。
if (product == 0) {this.wait();
}
消费者A进入pop方法
消费者A使用产品,剩余0件产品
  1. 消费者A的CPU时间片未结束,继续进入pop方法,但此时已没有产品了,进入阻塞队列并释放锁
消费者A进入pop方法
  1. 由于步骤3已经唤醒了生产者A线程(注意生产者A停留在if代码块中),此时生产者A直接跳出 if 代码块并添加产品并唤醒其他线程
生产者A添加产品,剩余1件产品
  1. 生产者A时间片未结束,继续进入push方法,此时有产品,进入阻塞队列
生产者A进入push方法
  1. 生产者B进入push方法,此时有产品,进入阻塞队列
生产者B进入push方法
  1. 在步骤5中唤醒了阻塞队列中的消费者A线程,此时消费者A跳出 if 代码块消费产品并唤醒了生产者A线程、生产者B线程,由于时间片未结束,消费者A继续进入pop方法,但此时已经没有产品了,进入阻塞队列
消费者A使用产品,剩余0件产品
消费者A进入pop方法
  1. 经过这么久,终于要到发生同步错误的地方了!!!注意步骤8中消费者A唤醒了位于阻塞队列中的生产者A线程和生产者B线程,而这两个线程此时停留在if代码块中。
    首先 CPU时间片给到了生产者B,生产者B生产了一个产品,但时间片未结束,继续进入push方法,此时已有产品,因此生产者B停留在this.wait()处
if (product > 0) {this.wait();
}
生产者B添加产品,剩余1件产品
生产者B进入push方法
  1. 此时CPU时间片给到了生产者A,生产者A跳出if判断条件,添加一个产品(此时产品变为两个)并唤醒其他线程(生产者B线程又被唤醒了),同样CPU时间片未结束会产生和步骤9生产者线程B同样的操作
生产者A添加产品,剩余2件产品
生产者A进入push方法
  1. 在步骤10中生产者B线程又被唤醒,此时CPU时间片又给到生产者B,生产者跳出 if 代码块并生产一个产品(此时产品变为3个)…
生产者B添加产品,剩余3件产品

如此一来,两个生产者就有可能一直往复生产下去,产品数量可能变得很大。同时,若两个消费者一直交替消费产品,那产品数量可能就会出现负数的情况。如下面运行结果:

...
消费者B进入pop方法
消费者B使用产品,剩余0件产品
消费者B进入pop方法
消费者A使用产品,剩余-1件产品
消费者A进入pop方法
消费者A使用产品,剩余-2件产品
消费者B使用产品,剩余-3件产品
生产者B添加产品,剩余-2件产品
生产者B进入push方法
生产者B添加产品,剩余-1件产品
生产者A添加产品,剩余0件产品
...

为什么会产生虚假唤醒?

从上面的例子可以看出,同步失败的主要原因有以下几个点:

  1. 生产者唤醒了所有处于阻塞队列中的线程,我们希望的是生产者A唤醒的应该是两个消费者,而不是唤醒了生产者B
  2. 我们都知道,wait方法的作用是将线程停止执行并送入到阻塞队列中,但是wait方法还有一个操作就是释放锁。因此当生产者A执行wait方法时,该线程就会把它持有的对象锁释放,这样生产者B就可以拿到锁进入synchronized修饰的push方法中,即使它被卡在if判断,但被唤醒后它就会又添加一个产品了。

如何解决虚假唤醒?

从上面分析可以知道导致虚假唤醒的原因主要就是一个线程直接在if代码块中被唤醒了,这时它已经跳过了if判断。我们只需要将if判断改为while,这样线程就会被重复判断而不再会跳出判断代码块,从而不会产生虚假唤醒这种情况了。

改动后的代码:

public synchronized void push() throws InterruptedException {System.out.println(Thread.currentThread().getName() + "进入push方法");while (product > 0) {this.wait();}product++;System.out.println(Thread.currentThread().getName() + "添加产品,剩余" + product + "件产品");this.notifyAll();}public synchronized void pop() throws InterruptedException {System.out.println(Thread.currentThread().getName() + "进入pop方法");while (product == 0) {this.wait();}product--;System.out.println(Thread.currentThread().getName() + "使用产品,剩余" + product + "件产品");this.notifyAll();}

执行结果如下:

生产者A进入push方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
消费者A进入pop方法
消费者A使用产品,剩余0件产品
消费者A进入pop方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
消费者A使用产品,剩余0件产品
消费者A进入pop方法
生产者A添加产品,剩余1件产品
生产者A进入push方法
消费者A使用产品,剩余0件产品
...

可以看出,无论CPU时间片给到哪个线程都不会再发生虚假唤醒了

参考:

  1. 什么是Java虚假唤醒及如何避免虚假唤醒?《多线程学习之十四》
  2. Java中Synchronized的用法(简单介绍)
  3. java并发编程:wait()和sleep的区别

什么是虚假唤醒?为什么会产生虚假唤醒?相关推荐

  1. 锁 唤醒_Java笔记|等待唤醒机制

    等待唤醒案例分析:线程之间的通信 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待) 创建一个老板线程(生产者):花了5 ...

  2. 什么是局域网唤醒? 如何启用局域网唤醒?

    Computers are generally started with the power on/off buttons. But in some cases, we may need to boo ...

  3. kali锁屏后无法唤醒_联想电脑锁屏后无法唤醒_win10联想休眠怎么唤醒黑屏图文步骤...

    2020-02-15 12:59:37 大家在使用电脑时为了保护电脑的安全,我们一般会设置密码.那么之后我们开机或者唤醒睡眠的时候,往往会先弹出一个锁屏界面.但有的网友却觉得这样很是麻烦,想要关闭锁屏 ...

  4. 虚假发货的定义,虚假发货的情形有哪些,虚假交易的处罚

    一.虚假发货定义 虚假发货是指商家上传至拼多多后台的商品物流单号对应的物流信息存在明显异常情形,及商家未真实发货的其他情形. 二.虚假发货情形 商家上传商品物流单号(包括转运后物流单号)后的24小时内 ...

  5. win10 计算机休眠后无法唤醒,win10休眠后无法唤醒怎么办 win10系统怎么设置休眠时间...

    现在很多用户都安装了win10系统,对于很多刚安装win10的用户有很多功能不知道怎么设置,比如说电脑的休眠时间,如果不设置的话很快电脑就会进入休眠时间,如果电脑出现故障的话,就会引起电脑长期休眠无法 ...

  6. java线程唤醒线程_Java中如何唤醒“指定的“某个线程

    群里有学C#转Java的小朋友问:Java中有木有一个办法唤醒一个"指定的"线程. 熟悉线程操作的小朋友应该知道,Java中线程的挂起和唤醒一般用synchronized + wa ...

  7. 如何唤醒计算机,待机后如何唤醒计算机?介绍睡眠待机的优势

    当我们将鼠标拖到习惯的位置时,我们会发现计算机实际上包括三个选项:关机,重新启动和睡眠.睡眠意味着您暂时不需要使用计算机,但是在需要时,界面可以保持不变.但是很不知道睡觉后怎么醒?如果您无法成功唤醒怎 ...

  8. java 等待唤醒机制,Java线程等待唤醒机制

    记录面试过程中被问到的几个需要手写代码的小案例 1.请手写出线程的等待唤醒机制 案例中两个线程:SyncSetThread设置学生信息,SyncGetThread用来获取学生信息,在Student实体 ...

  9. 台式计算机睡眠了怎么唤醒,台式电脑睡眠了怎么唤醒

    电脑闲置的时候,除了关机之外,用户还可以考虑将电脑设为休眠或睡眠状态,这样既可以保留原有的东西,也可以省电,那么你知道吗?下面是小编整理的一些关于台式电脑睡眠了唤醒的相关资料,供你参考. 台式电脑睡眠 ...

  10. win10睡眠按啥键唤醒_防止Windows10自动唤醒,就用这4招,维修电脑必知

    在Windows 10上,休眠或睡眠功能是非常出色的功能,可以让计算机快速地恢复到关闭时的状态,甚至可以节省能源.但是,使用这些功能也会导致系统在半夜或不使用时自动唤醒. 操作系统有多个电源状态,包括 ...

最新文章

  1. MaxCompute2.0新功能介绍
  2. 赠书:Apache顶级项目,SkyWalking为何一枝独秀?
  3. 《C++标准程序库》读书笔记(三)
  4. 1万小时贫穷定律:为什么你越忙越穷?
  5. PHP经常使用正則表達式汇总
  6. Python3 爬虫学习笔记 C05 【Selenium + 无界面浏览器】
  7. Apache Dubbo 3.0.0 正式发布!全面拥抱云原生
  8. PHP 查看系统文件时间戳
  9. 学校学生工科学生接私活_为什么我要在学校教年轻的学生如何编码
  10. 如何实现共享软件网络授权认证,包括注册新用户、登录、修改密码等操作
  11. python面向对象编程第2版_python面向对象编程(2),之,二
  12. 10种开发以及改善应用的低成本方法
  13. Google推出免费公共域名解析DNS服务
  14. 【BZOJ 4516】生成魔咒
  15. Unity的属性注入
  16. installanywhere's LAX Properties
  17. C程序设计--指针(对 “ 数组 ” 进行输入输出操作)
  18. 思科CCNP网络工程师 和思科CCIE网络工程师考试常见问题GRE虚拟专用网络详解
  19. 使用阿里云Ubuntu搭建代理服务器
  20. The CUDA compiler identification is unknown 解决

热门文章

  1. 成都迎来史诗级架构运维峰会,三大女神降临,送海量技术图书(8折优惠仅剩一周)...
  2. txt中的数据导入matlab中画图,excel怎样导入表格数据-如何将excel表格中大量数据导入matlab中并作图...
  3. 【3D目标分类】PCT:Point Cloud Transformer
  4. 高通工具SnapdragonProfiler的安装和基本使用说明
  5. 1、制作静态网页,用于个人信息的提交前验证。
  6. Android内存泄漏的简单检查与分析方法
  7. html里显示中文乱码的原因及解决办法
  8. 机房水题欢乐赛 2016-01-31
  9. 从字符串中提取出汉字?
  10. G120变频器在通信恢复后如何自动复位总线故障