每篇一句

提升码农亩产,掰直码农方向,授人以渔,功德无量,利在千秋

前言

本文主要讲述一个概念:虚假唤醒(spurious wakeup)。

在并发编程中,我们可能在实践中并没有遇到过,但是它确实存在,概率较低,但一旦出现,问题就非常的大。

比如我们给方法上锁,经常会使用到this.wait()的方式,但是此方法JDK官方在doc文档里已经给我们说明了:它是有可能出现虚假唤醒现象的,如下截图我是在JDK官方的doc文档截的

大致的意思如下:

线程也可以在不被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但是应用程序必须通过测试导致线程被唤醒的条件来防止这种情况,并且如果条件不满足则继续等待。换句话说,等待应该总是在循环中发生。

代码示例

现在通过代码的方式,来演示出什么叫做虚假唤醒,这样能够更好的理解
先构建三个类:店员 生产者 消费者

// 店员类:负责进货和售货
class Clerk{//TOTAL表示我的店最大可以容纳的总量private static final int TOTAL=1; //数字取1是为了放大问题 private int num=0; //店里当前的货物量public synchronized void get() { //店员进货  每次进货一个if(num >= TOTAL) {System.out.println("库存已满,无法进货");try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}} else {System.out.println(Thread.currentThread().getName()+" : "+ (num++));this.notifyAll(); }}public synchronized void sale() { //店员卖货 每次卖掉一个货if(num<=0) {System.out.println("库存已空,无法卖货");try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {System.out.println(Thread.currentThread().getName()+" : "+(num--));this.notifyAll();}}
}// 生产者 可以有很多生产者卖货给这个店员
class Producer implements Runnable{private Clerk clerk;public Producer(Clerk clerk) {this.clerk=clerk;}@Overridepublic void run() {for (int i = 0; i<20; i++) {try {Thread.sleep(200); //放大问题} catch (InterruptedException e) {e.printStackTrace();}clerk.get();}}
}//消费者:可以很多消费者找店员买货
class Consumer implements Runnable{private Clerk clerk;public Consumer(Clerk clerk) {this.clerk=clerk;}@Overridepublic void run() {for (int i = 0; i<20; i++) {clerk.sale();}}
}

先我们只用一个生产者,一个消费者试试:

public static void main(String[] args) {Clerk clerk=new Clerk();Producer producer=new Producer(clerk);Consumer consumer=new Consumer(clerk);new Thread(producer,"生产者A1").start();new Thread(consumer,"消费者B1").start();}

各位读者可以先看看代码,猜猜结果。

代码咋一看,其实真的没什么问题,但是因为我们通过sleep把问题放大了,所以我们运行一下,**竟然发现控制台一直都没有结束。**而我们这里是for20次循环,按理来说程序最终会终止,可情况恰恰相反。

问题分析

分析产生上面控制台一直都没有停的原因:

问题产生的根源是,由于生产者现象睡眠了200毫秒,因而可能产生的情况是最后消费者线程循环走完了然后就真的结束了,而生产者线程由于wait没有线程来唤醒,所以最终导致一直等待,因而程序不会结束,控制台就不终止

解决方案:

一:把同步方法的else去掉了,那么无论最终哪个线程先走完,都会执行wait后面的方法,即它在结束前会唤醒等待的线程,因而这个线程最终也会完整的执行完,最后程序终止。

if(num<=0) {System.out.println("库存已空,无法卖货");try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+" : "+(num--));this.notifyAll();... //另外一个同步方法等其余代码省略

这种办法貌似解决了我们的问题,程序也正常的终止了。但是我们再加一组生产者、消费者试试:

public static void main(String[] args) {Clerk clerk=new Clerk();Producer producer=new Producer(clerk);Consumer consumer=new Consumer(clerk);Producer producer2=new Producer(clerk);Consumer consumer2=new Consumer(clerk);new Thread(producer,"生产者A1").start();new Thread(consumer,"消费者B1").start();new Thread(producer2,"生产者A2").start();new Thread(consumer2,"消费者B2").start();
}

运行,竟然发现出现了产品为负数的情况。这,就,尴尬了。肯定不合适。因为我们把esle放开了,所以每次都notifyAll()出现了虚假唤醒现象。

因为有可能num==0,然后两个消费者线程都wait,此时生产者执行num++后,在唤醒却是唤醒了所有等待的线程,此时这两个消费者线程抢占资源后立马执行wait之后的操作,即num–就会出现产品为负的情况

为了再表面这种现象,我们就要使用JDK中DOC给我们推荐的方法了:wait()方法往往建议都使用在while循环里面,因此我们继续改进:

把if改成while即可:

while(num>=TOTAL) {}

这样我们再次运行,完美,没任何毛病。不管我们用多少个生产者、消费者,都没有问题了。

总结

按照官方JDK说的,虚假唤醒在wait的时候是有可能发生的,因此建议都放在while循环里,这样能够完全的避免问题。


关注A哥

Author A哥(YourBatman)
个人站点 www.yourbatman.cn
E-mail yourbatman@qq.com
微 信 fsx641385712
活跃平台
公众号 BAT的乌托邦(ID:BAT-utopia)
知识星球 BAT的乌托邦
每日文章推荐 每日文章推荐

【小家java】JUC并发编程之:虚假唤醒(spurious wakeup)以及推荐的解决方案相关推荐

  1. 多线程并发编程需要注意虚假唤醒Spurious wakeup

    虚假唤醒  Spurious wakeup 如果等待线程在没有通知被调用的情况下唤醒,则称为Spurious wakeup. 解决方案就是: 使用while条件判断,更好的方案是避免使用wait这种低 ...

  2. 【java 高并发编程之JUC】2w字带你JUC从入门到精通

    点击查看脑图目录地址,实时更新 1 什么是 JUC 1.1 JUC 简介 在 Java 中,线程部分是一个重点,本篇文章说的 JUC 也是关于线程的.JUC 就是 java.util .concurr ...

  3. 【java 高并发编程之JUC】高阶JUC特性总结

    1 线程中断机制 1.1 什么是中断? 首先 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止.所以,Thread.stop, Thread.suspend, Thread.res ...

  4. JUC并发编程之Java线程(二)

    二.Java线程 2.1 创建和运行线程 方法一:Thread创建线程方式: 继承Thread类 匿名内部类方式 public class CreateThread01 {public static ...

  5. JUC并发编程之Callable接口、JUC三大辅助类

    目录 8. Callable接口 8.1 创建线程的多种方式 8.2 概述 8.3 用Callable接口创建Thred线程 8.4 小结(重点) 9. JUC 三大辅助类 9.1 概述 9.2 减少 ...

  6. [C++11 多线程同步] --- 条件变量的那些坑【条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)】

    1 条件变量的信号丢失 1.1 条件变量的信号丢失场景重现 拿生产者和消费者模型举例,看一段示例代码: #include <iostream> #include <vector> ...

  7. java线程安全的set_Java并发编程之set集合的线程安全类你知道吗

    Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥并发编程学习>系列之<并发集合系列& ...

  8. java 时间戳_Java并发编程之CAS三CAS的缺点 及解决办法

    Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...

  9. Java并发编程之CAS第三篇-CAS的缺点

    Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...

  10. JUC 高并发编程之JUC三大辅助类

    JUC 高并发编程之JUC三大辅助类 JUC 中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过 多时 Lock 锁的频繁操作.这三种辅助类为: CountDownLatch: 减少计数 ...

最新文章

  1. 2019年,我终于知道86版西游记到底好在哪里了
  2. 2019-1-17王志颖 c语言作业
  3. 聚类(上)K-mean算法
  4. matlab操作入门实验报告,matlab操作实验报告
  5. CLI下的网页浏览器之二——Lynx
  6. *【CodeForces - 122D】Lucky Transformation(字符串问题,思维剪枝,优化,有坑,需注意的问题if的层次总结)
  7. 帆软独家:数字化转型打造企业数据战斗力
  8. 05-IntelliJ IDEA清空项目缓存
  9. 有大招儿?请收下这份关于数据与智能的晋级攻略!
  10. Git首次提交代码到仓库步骤(资料)
  11. 编译OpenJDK8:[HotspotWrapper.gmk:45:/cygdrive/e/hub/openjdk/jdk8u/build/windows-x86_64-normal-server-
  12. paip.c++ qt 项目工程互相引用的方法
  13. 自适应滤波器之块自适应滤波器
  14. 测试听力口语软件,上、英语系学姐最全整理的34个英语学习App 针对听力、口语、阅读...
  15. HO-PLGA-COOR,酯封端聚(D,L-丙交酯-co-乙交酯)共聚物
  16. 利用python对gif图片进行压缩处理,简单案例
  17. ArcGIS配图/地图符号化的一些技巧与相关资料
  18. linux将目录打包压缩,linux tar (打包.压缩.解压缩)命令说明 | tar如何解压文件到指定的目录?...
  19. 【云原生】Hadoop HA on k8s 环境部署
  20. 用计算机解开手机密码,手机忘记锁屏密码?维修老师傅教你一招,按下这里就能解开...

热门文章

  1. OpenHarmony 2.0和HarmonyOS发布会快评
  2. 张晨北京科技大学计算机,北京大学2010拟初取推荐免试研究生公示名单.doc
  3. 云计算-平台架构-开源-OpenStack
  4. 城市历年人均GDP API数据接口
  5. 洛必达法则介绍及使用注意点
  6. 程序员必须知道的八件事
  7. crmeb安装教程说明
  8. win10计算机证书管理器,Win10系统如何设置管理文件加密证书
  9. 触发器的三种触发方式:电平触发、边沿触发、脉冲触发区别
  10. 族谱软件系统的使用介绍