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 ...
随机推荐
- Azure Data Factory(三)集成 Azure Devops 实现CI/CD
一,引言 由于上一节文章内容过长,无法分享Azure Data Factory 的持续集成,持续发布.今天将着重介绍一下在使用 Azure DevOps Pipeline 发布,自动进行持续集成,并且 ...
- MySQL全面瓦解6:查询的基本操作
概述 提到查询,就回到我们第四篇的SQL语言分类了,DQL(Data QueryLanguage),也就是数据查询语言,实际就是从数据库中获取数据的一种命令方式.我们给数据库发送一个查询语句的命令,数 ...
- How to Convert and Import VHD to VMDK (VMWare)
VHD or Virtual Hard Disk is the disk image format used by Microsoft virtualization software such as ...
- Java项目——嗖嗖移动业务大厅
嗖嗖移动业务大厅包类(如下图): SosoMgr: 1 package cn.biz; 2 3 import java.util.Scanner; 4 5 import cn.common.Commo ...
- 转载:WIFI无线协议802.11a/b/g/n/ac的演变以及区别
WIFI无线协议802.11a/b/g/n/ac的演变以及区别 版权声明:版权所有,转载须注明出处. https://blog.csdn.net/Brouce__Lee/article/details ...
- 人体动作捕捉格式之BVH
BVH简介 BVH是BioVision公司推出的一种人体动作捕捉文件格式.这种文件以节点为核心元素,记录连续数帧内人体骨架的运动. BVH=? 研究一个东西的时候我比较喜欢先研究它的名字.BVH可以认 ...
- StringUtil字符串工具-功能扩展
对java的String类的一些小补充. public class StringUtil { /** 将一个字符串的首字母大写然后返回. @param str 要处理的目标字符串. @return S ...
- JavaScript探秘:可执行的上下文堆栈
这一系列的文章,挺不错的,值得收藏细读 JavaScript探秘:可执行的上下文堆栈
- QQ群web前端分析二——第一印象
对QQ群WEB进行前端分析 入口是 http://qun.qzone.qq.com/ 以下为第一印象,主要是从我的理解上找问题. ----------------------------------- ...
- Goldstone's theorem(转载)
Goldstone's theorem是凝聚态物理中的重要定理之一.简单来说,定理指出:每个自发对称破缺都对应一个无质量的玻色子(准粒子),或者说一个zero mode. 看过文章后,我个人理解这其实 ...