本文分享 wait()  的虚假唤醒(Spurious Wakeups)问题,会说明什么是虚假唤醒,以及如何解决。

先看一下相关的 java doc:

java doc 说由于中断和虚假唤醒可能会发生,所以总是要在循环里面调用。由此可见解决办法其实很简单,只要放在循环里面就行了!

接下来我要写一段生产者消费者往仓库存取产品的相关代码,借着这个代码来说明为什么是虚假唤醒。

代码的逻辑也很简单,就是有一个仓库 Store 负责存放产品,每次只能放一个,一个 Producer 负责生产,一个 Consumer 负责消费。代码如下(为了便于浏览,所有类都写在了同一个文件中):

public class TestProducerAndConsumer{public static void main(String[] args){Store store = new Store();Producer producer = new Producer(store);Consumer consumer = new Consumer(store);new Thread(consumer, "消费者A").start();new Thread(consumer, "消费者B").start();new Thread(producer,"生产者A").start();new Thread(producer,"生产者B").start();}}class Store{private int product = 0;public synchronized void put(){if(product >= 1) {System.out.println(Thread.currentThread().getName()+ "来了,但产品已满!");try{this.wait();} catch (InterruptedException e) {}}System.out.println(Thread.currentThread().getName()+ "操作后,库存为: " + ++product);this.notifyAll();}public synchronized void get(){if(product <= 0) {System.out.println(Thread.currentThread().getName()+ "来了,但缺货!");try{this.wait();}catch (InterruptedException e){}}System.out.println(Thread.currentThread().getName()+ "操作后,库存为: " + --product);this.notifyAll();}}class Consumer implements Runnable{private Store store;public Consumer(Store store){this.store = store;}public void run(){store.get();}}class Producer implements Runnable{private Store store;public Producer(Store store){this.store = store;}public void run(){store.put();}}

运行结果:

我们可以看到库存出现了一个 -1 的情况,这很显然不符合实际,因为库存不可能为负数。

为了避免库存出现负数,我已经在程序的第 42 行加了判断,如果产品的数量小于 0,那么消费者就会转为等待状态,那么这个 -1 是怎么出现的呢?

结合上下两张图我们来分析一下:

分析一下程序的运行情况:

1)消费者 A 抢到锁然后调用 get,产品数量为 0,则转为等待状态并释放锁;

2)消费者 B 抢到锁然后调用 get,产品数量为 0,则转为等待状态并释放锁;

3)生产者 A 抢到锁然后调用 put,将产品数量变为 1,并调用 notifyAll 方法将消费者 A 和消费者 B 唤醒转为就绪状态,并释放锁;

4)消费者 B 抢到锁然后从 wait 位置继续向下运行,将产品数量变为 0,方法执行完毕释放锁;

5)消费者 A 抢到锁然后从 wait 位置继续向下运行,将产品数量变为 -1,方法执行完毕释放锁;

以上就是 -1 产生的过程,我们可以看出第 5 步中消费者 A 这次被唤醒有点多余了,虽然它刚 "醒" 时产品数是大于 1,但被消费者 B 先行一步,把产品消费了,轮到它消费时已经不符合消费的条件了,所以再消费就造成了错误。

解决办法就像上面说的一样,需要把 wait 放到循环里面,所以我们将第 42 行的 if 改成 while. 这样当第 5 步中的消费者 A 从 wait 位置继续向下运行,会再进行一次产品数量的判断,会发现产品的数量已经不符合它消费的条件了,从而再次转为等待状态。

类似的第 22 行的 if 也应该改为 while。虽然当前程序它不会运行出问题,但将 product 初始值设为 1,在让两个生产者先启动,则很容易出现类似错误。

private int product = 1;

public static void main(String[] args) {Store store = new Store();Producer producer = new Producer(store);Consumer consumer = new Consumer(store);new Thread(producer,"生产者A").start();new Thread(producer,"生产者B").start();new Thread(consumer, "消费者A").start();new Thread(consumer, "消费者B").start();}

类似的库存为 2 在本程序中也不是我们期望出现的情况。

本次分享完毕,感谢阅读!

java线程打水问题_Java 多线程 wait() 虚假唤醒问题相关推荐

  1. java 线程的基本概念_Java多线程——基本概念

    线程和多线程 程序:是一段静态的代码,是应用软件执行的蓝本 进程:是程序的一次动态执行过程,它对应了从代码加载.执行至执行完毕的一个完整过程,这个过程也是进程本身从产生.发展至消亡的过程 线程:是比进 ...

  2. java线程休眠sleep函数_Java多线程中sleep()方法详解及面试题

    一. Java线程生命周期(五个阶段) 新建状态就绪状态运行状态阻塞状态死亡状态 如图 二.sleep方法 API中的解释 static voidsleep(long millis) 使当前正在执行的 ...

  3. java 线程的基本概念_Java多线程——多线程的基本概念和使用

    一.进程和线程的基础知识 1.进程和线程的概念 进程:运行中的应用程序称为进程,拥有系统资源(cpu.内存) 线程:进程中的一段代码,一个进程中可以有多段代码.本身不拥有资源(共享所在进程的资源) 在 ...

  4. java线程的内存模型_java多线程内存模型

    java多线程内存模型: 可见性 要实现共享变量的可见性,必须保证两点: 1.线程修改后的共享变量能够及时从工作内存刷新到主内存中: 2.其他线程能够及时把共享变量的最新值从主内存更新到自己的工作内存 ...

  5. java 线程同步的方法_Java多线程同步方法

    Java多线程同步方法 package com.wkcto.intrinsiclock; /** * synchronized同步实例方法 * 把整个方法体作为同步代码块 * 默认的锁对象是this对 ...

  6. java 线程死锁简单例子_java 多线程死锁详解及简单实例

    java 多线程死锁 相信有过多线程编程经验的朋友,都吃过死锁的苦.除非你不使用多线程,否则死锁的可能性会一直存在.为什么会出现死锁呢?我想原因主要有下面几个方面: (1)个人使用锁的经验差异 (2) ...

  7. java线程中的死锁_Java多线程中的死锁 - Break易站

    Java 多线程 synchronized关键字用于使类或方法线程安全,这意味着只有一个线程可以锁定同步方法并使用它,其他线程必须等到锁定释放并且其中任何一个获得该锁定. 如果我们的程序在多线程环境中 ...

  8. java线程看不进去_Java多线程和并发基础面试问答,看过后你不会后悔

    第一:Java多线程面试问题 1:过程和线程之间有什么不合? 一个过程是一个自力(self contained)的运行情况,它可以被看作一个法度榜样或者一个应用.而线程是在过程中履行的一个义务.Jav ...

  9. java线程的实现方法_Java多线程的四种实现方式

    1.Java多线程实现的方式有四种: 1.继承Thread类,重写run方法 2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的targ ...

最新文章

  1. jsp中静态include和动态include的区别
  2. 第十届蓝桥杯java B组—试题C 数列求值
  3. 【GDKOI2003】最大公共子串
  4. 今日小程序推荐:香蕉打码-二维码随意生成
  5. 2021,我在枯燥乏味中寻找坚持下去的理由
  6. 六自由度机器人逆向运动学_【课程笔记】Notes for Robotics/机器人学 (Part1)
  7. 化浆池是什么东西_一种双工位浆化池的制作方法
  8. 分布式事务实践 解决数据一致性 分布式事务实现:Event Sourcing模式
  9. DISCUZ编辑器工具栏图标不显示
  10. 易软门诊管理软件php,易软门诊管理系统
  11. MC辨析(蒙特卡洛)
  12. Python自动化运行合成大西瓜|附小游戏地址
  13. 半路接手项目,做好“沟通”很重要
  14. 一万多字的windows历史
  15. cluster by、group by操作
  16. 论文阅读汇总(3)-【篇数:50】
  17. pe系统如何读取手机_什么是otg(pe系统如何读取手机)
  18. 玩转Kafka—SpringGo整合Kafka
  19. 小程序跳转无反应解决方法
  20. 选下拉框的的值对应上传相应的图片_excel表格下拉菜单调用对应数据,如何在excel中实现,选择下拉菜单某一项,该表格中就出现选项对应的数据?...

热门文章

  1. Keras-1 学习Keras,从Hello World开始
  2. 总结Django一些开发经验
  3. Azure下通过Powreshell批量添加、删除VM终结点
  4. Vue2学习小记-给Vue2路由导航钩子和axios拦截器做个封装
  5. phpcms v9 打开网站特别慢 增加数据库缓存方法
  6. win8网络受限官方解决办法
  7. 【转】struct epoll_event
  8. C++/C学习笔记(十一)——存储分配器和适配器
  9. mysql8.0windows,Windows下mysql 8.0.12 安装详细教程
  10. c 怎么配置oracle,cjdbc入门配置oracle