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,算法等干货的技术公众号。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
熬夜7天吐血整理,适合Vue用户的React详细教程
我的日常工作中使用的是Vue,对于React只是做过简单的了解,并没有做过深入学习。趁着假期,我决定好好学一学React,今天这篇文章就是我在学习React之后,将React与Vue的用法做的一个对比,通过这个对比,方便使用Vue的小伙伴可以快速将Vue中的写法转换为React的写法。 插槽,在React中没找到?? 在使用Vue的时候,插槽是一个特别常用的功能,通过定义插槽,可以在调用组件的时候将外部的内容传入到组件内部,显示到指定的位置。在Vue中,插槽分为默认插槽,具名插槽和作用域插槽。其实不仅仅Vue,在React中其实也有类似插槽的功能,只是名字不叫做插槽,下面我将通过举例来说明。 默认插槽 现在项目需要开发一个卡片组件,如下图所示,卡片可以指定标题,然后卡片内容可以用户自定义,这时候对于卡片内容来说,就可以使用插槽来实现,下面我们就分别使用Vue和React来实现这个功能 Vue实现 首先实现一个card组件,如下代码所示 <template> <div class="card"> <div class="card__title"> &l...
- 下一篇
「五大常用算法」一文图解分治算法和思想
前言 分治算法(divide and conquer)是五大常用算法(分治算法、动态规划算法、贪心算法、回溯法、分治界限法)之一,很多人在平时学习中可能只是知道分治算法,但是可能并没有系统的学习分治算法,本篇就带你较为全面的去认识和了解分治算法。 在学习分治算法之前,问你一个问题,相信大家小时候都有存钱罐的经历,父母亲人如果给钱都会往自己的宝藏中存钱,我们每隔一段时间都会清点清点钱。但是一堆钱让你处理起来你可能觉得很复杂,因为数据相对于大脑有点庞大了,并且很容易算错,你可能会将它先分成几个小份算,然后再叠加起来计算总和就获得这堆钱的总数了 当然如果你觉得各个部分钱数量还是太大,你依然可以进行划分然后合并,我们之所以这么多是因为: 计算每个小堆钱的方式和计算最大堆钱的方式是相同的(区别在于体量上) 然后大堆钱总和其实就是小堆钱结果之和。这样其实就有一种分治的思想。 当然这些钱都是想出来的…… 分治算法介绍 分治算法是用了分治思想的一种算法,什么是分治? 分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS关闭SELinux安全模块
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Hadoop3单机部署,实现最简伪集群