Java多线程中的虚假唤醒和如何避免
先来看一个例子
一个卖面的面馆,有一个做面的厨师和一个吃面的食客,需要保证,厨师做一碗面,食客吃一碗面,不能一次性多做几碗面,更不能没有面的时候吃面;按照上述操作,进行十轮做面吃面的操作。
用代码说话
首先我们需要有一个资源类,里面包含面的数量,做面操作,吃面操作;
当面的数量为0时,厨师才做面,做完面,需要唤醒等待的食客,否则厨师需要等待食客吃完面才能做面;
当面的数量不为0时,食客才能吃面,吃完面需要唤醒正在等待的厨师,否则食客需要等待厨师做完面才能吃面;
然后在主类中,我们创建一个厨师线程进行10次做面,一个食客线程进行10次吃面;
代码如下:
package com.duoxiancheng.code;
/**
* @user: code随笔
*/
class Noodles{
//面的数量
private int num = 0;
//做面方法
public synchronized void makeNoodles() throws InterruptedException {
//如果面的数量不为0,则等待食客吃完面再做面
if(num != 0){
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"做好了一份面,当前有"+num+"份面");
//面做好后,唤醒食客来吃
this.notifyAll();
}
//吃面方法
public synchronized void eatNoodles() throws InterruptedException {
//如果面的数量为0,则等待厨师做完面再吃面
if(num == 0){
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"吃了一份面,当前有"+num+"份面");
//吃完则唤醒厨师来做面
this.notifyAll();
}
}
public class Test {
public static void main(String[] args) {
Noodles noodles = new Noodles();
new Thread(new Runnable(){
@Override
public void run() {
try {
for (int i = 0; i < 10 ; i++) {
noodles.makeNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"厨师A").start();
new Thread(new Runnable(){
@Override
public void run() {
try {
for (int i = 0; i < 10 ; i++) {
noodles.eatNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"食客甲").start();
}
}
输出如下:

可以见到是交替输出的;
如果有两个厨师,两个食客,都进行10次循环呢?
Noodles类的代码不用动,在主类中多创建两个线程即可,主类代码如下:
public class Test {
public static void main(String[] args) {
Noodles noodles = new Noodles();
new Thread(new Runnable(){
@Override
public void run() {
try {
for (int i = 0; i < 10 ; i++) {
noodles.makeNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"厨师A").start();
new Thread(new Runnable(){
@Override
public void run() {
try {
for (int i = 0; i < 10 ; i++) {
noodles.makeNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"厨师B").start();
new Thread(new Runnable(){
@Override
public void run() {
try {
for (int i = 0; i < 10 ; i++) {
noodles.eatNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"食客甲").start();
new Thread(new Runnable(){
@Override
public void run() {
try {
for (int i = 0; i < 10 ; i++) {
noodles.eatNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"食客乙").start();
}
}
此时输出如下:

虚假唤醒
上面的问题就是"虚假唤醒"。
当我们只有一个厨师一个食客时,只能是厨师做面或者食客吃面,并没有其他情况;
但是当有两个厨师,两个食客时,就会出现下面的问题:
- 初始状态

- 厨师A得到操作权,发现面的数量为0,可以做面,面的份数+1,然后唤醒所有线程;

- 厨师B得到操作权,发现面的数量为1,不可以做面,执行wait操作;

- 厨师A得到操作权,发现面的数量为1,不可以做面,执行wait操作;

- 食客甲得到操作权,发现面的数量为1,可以吃面,吃完面后面的数量-1,并唤醒所有线程;

6. 此时厨师A得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;

7. 此时厨师B得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;

这便是虚假唤醒,还有其他的情况,读者可以尝试画画图分析分析。
解决方法
出现虚假唤醒的原因是从阻塞态到就绪态再到运行态没有进行判断,我们只需要让其每次得到操作权时都进行判断就可以了;
所以将
if(num != 0){
this.wait();
}
改为
while(num != 0){
this.wait();
}
将
if(num == 0){
this.wait();
}
改为
while(num == 0){
this.wait();
}
即可。
微信搜索:code随笔 欢迎关注乐于输出Java,算法等干货的技术公众号。
Java多线程中的虚假唤醒和如何避免的更多相关文章
- java 多线程—— 线程等待与唤醒
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- java 多线程中的wait方法的详解
java多线程中的实现方式存在两种: 方式一:使用继承方式 例如: PersonTest extends Thread{ String name; public PersonTest(String n ...
- Java多线程中的常用方法
本文将带你讲诉Java多线程中的常用方法 Java多线程中的常用方法有如下几个 start,run,sleep,wait,notify,notifyAll,join,isAlive,current ...
- java多线程中的三种特性
java多线程中的三种特性 原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并 ...
- java多线程中并发集合和同步集合有哪些?区别是什么?
java多线程中并发集合和同步集合有哪些? hashmap 是非同步的,故在多线程中是线程不安全的,不过也可以使用 同步类来进行包装: 包装类Collections.synchronizedMap() ...
- java多线程中最佳的实践方案是什么?
java多线程中最佳的实践方案是什么? 给你的线程起个有意义的名字.这样可以方便找bug或追踪.OrderProcessor, QuoteProcessor or TradeProcessor 这种名 ...
- Java多线程中的竞争条件、锁以及同步的概念
竞争条件 1.竞争条件: 在java多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生“竞争条件”的现象.这种现象产生的根本原因是因为多个线程在对同一个数据进行操作,此时对该数据的操作 ...
- Java多线程中的死锁
Java多线程中的死锁 死锁产生的原因 线程死锁是指由两个以上的线程互相持有对方所需要的资源,导致线程处于等待状态,无法往前执行. 当线程进入对象的synchronized代码块时,便占有了资源,直到 ...
- Java多线程中线程间的通信
一.使用while方式来实现线程之间的通信 package com.ietree.multithread.sync; import java.util.ArrayList; import java.u ...
随机推荐
- python中的递归
python中的递归 关注公众号"轻松学编程"了解更多. 文章更改后地址:传送门 间接或直接调用自身的函数被称为递归函数. 间接: def func(): otherfunc() ...
- SQL 速查表
关系数据库,基于关系模型,使用关系(表)存储数据,同时定义了完整性约束.常见的关系数据库系统包括:Oracle.MySQL/MariaDB.SQL Server.PostgreSQL 等等. SQL, ...
- 重拾python所要知道的一些主干知识点
前言:因为有一段时间没有用python了,最近需要用到,只能回头过去看B站视频补一补,因为语言都是相通的,而且一些细节都可以去查表解决,所以呢,我们只需要知道一些python与其他语言的不同和常见的优 ...
- Unbuntu 18.04 LTS 环境下Python安装GDAL组件
Unbuntu 18.04 LTS 环境下Python安装GDAL组件 // 非必要 sudo add-apt-repository ppa:ubuntugis/ppa sudo apt-get up ...
- 深度学习中的Dropout
dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃.注意是暂时,对于随机梯度下降来说,由于是随机丢弃,故而每一个mini-batch都在训练不同的网络. ...
- waf 引擎 云原生平台tproxy 实现调研
了解了基本 云原生架构,不清楚的查看之前的文章:https://www.cnblogs.com/codestack/p/13914134.html 现在来看看云原生平台tproxy waf引擎串联实现 ...
- linux 异步I/O 信号
if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) { ngx_log_error(NGX_LOG_ALERT, cycl ...
- uniapp 证书 打包上线GooglePlay app自动升级
uniapp Android证书 打包上线GooglePlay app自动升级 1.Android证书申请 要安装jdk并配置环境变量. keytool -genkey -alias android ...
- Linux上传递文件到另外一个Linux服务器
现在的项目由于安全的需要,测试服务器被设置不能直接连接,想要连接的话,只能先登录一个服务器,然后以这个服务器为跳板,去登录另外一台真正的 服务器,即使是测试环境也只能这样操作.只能是相对来说安全一些. ...
- bluestore对象挂载到系统进行提取
前言 之前在filestore里面,pg是直接暴露到文件系统的,也就是可以直接进去查看或者拷贝,在极端情况下,多个osd无法启动,pg无法导出的时候,那么对pg内部对象的操作处理,是可以作为最后恢复数 ...