Wait-Notify机制

news2024/11/23 1:39:09

文章目录

    • 1. 简介
    • 2. 相关API
    • 3. wait notify的正确姿势
    • 4. 总结

1. 简介

回顾Minitor锁的结构:
在这里插入图片描述

  • Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间
  • BLOCKED线程会在Owner线程释放锁时唤醒
  • WAITING线程会在Owner调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争

2. 相关API

  1. Obj.wait():让进入object的Monitor的线程到WaitSet等待
  2. Obj.wait(long n):有时限的等待,到n毫秒后结束等待,或者在n毫秒内被唤醒
  3. Obj.notify():在object上正在waitSet等待的线程中挑哟个唤醒
  4. Obj.notifyAll():让object上正在waitSet等待的线程全部被唤醒

3. wait notify的正确姿势

  • sleep(long n)和wait(long n)的区别
  1. sleep是Thread的静态方法,而wait是Object的方法
  2. sleep不需要强制和synchronized配合使用,但wait需要
  3. sleep在睡眠的同时,不会释放对象锁,但wait在等待时会释放对象锁
  • step1

思考下面代码:

@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                if(!hasCigarette){
                    log.debug(("没烟,先歇一会!"));
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }
            }
        },"小南").start();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room){
                    log.debug("可以开始干活了!");
                }
            },"其它人").start();
        }
        new Thread(()->{
            //synchronized (room){
                hasCigarette=true;
                log.debug("烟到了哦!");
            //}
        },"送烟的").start();
    }
}

在这里插入图片描述
问题是,小南线程在睡眠的时候,其它线程全部在EntryList中阻塞等待,在高并发场景下,这种效率是很低的,所以我们需要改善这种情况。

  • Step 2

使用wait-notify解决上面问题

@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                if(!hasCigarette){
                    try {
                        log.debug(("没烟,先歇一会!"));
                         room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }
            }
        },"小南").start();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room){
                    log.debug("可以开始干活了!");
                }
            },"其它人").start();
        }
        new Thread(()->{
            synchronized (room){
                hasCigarette=true;
                log.debug("烟到了哦!");
                room.notify();
            }
        },"送烟的").start();
    }
}

在这里插入图片描述
上面代码初步解决了前面小南线程在等待烟的时候,所有等待room的线程都会阻塞的问题。但同时也引入了新的问题,假如不止小南线程在等待,还有其它线程在wait,那么送烟线程会错误的唤醒其它线程,而不是指定的小南线程

  • Step 3
@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                if(!hasCigarette){
                    try {
                        log.debug(("没烟,先歇一会!"));
                         room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }else {
                    log.debug("这活干不了!");
                }
            }
        },"小南").start();
        new Thread(()->{
            synchronized (room){
                log.debug("外买到了没?[{}]",hasTakeout);
                if(!hasTakeout){
                    try {
                        log.debug(("没外卖,先歇一会!"));
                        room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("外买到了没?[{}]",hasTakeout);
                if(hasTakeout){
                    log.debug("开始干活了!");
                }else{
                    log.debug("这活干不了!");
                }
            }
        },"小北").start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout=true;
                log.debug("外卖到了哦!");
                room.notify();
            }
        },"送外卖的").start();
    }
}

在这里插入图片描述
从上面结果我们可以看出,外卖的线程唤醒了小南线程,这就出现了虚假唤醒的情况,那么怎么解决这个问题呢,我们使用notifyAll可以解决上面问题。

@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                if(!hasCigarette){
                    try {
                        log.debug(("没烟,先歇一会!"));
                         room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }else {
                    log.debug("这活干不了!");
                }
            }
        },"小南").start();
        new Thread(()->{
            synchronized (room){
                log.debug("外买到了没?[{}]",hasTakeout);
                if(!hasTakeout){
                    try {
                        log.debug(("没外卖,先歇一会!"));
                        room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("外买到了没?[{}]",hasTakeout);
                if(hasTakeout){
                    log.debug("开始干活了!");
                }else{
                    log.debug("这活干不了!");
                }
            }
        },"小北").start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout=true;
                log.debug("外卖到了哦!");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}

在这里插入图片描述
虽然我们解决了小北的问题,小南的问题我们还是没有解决,由于小南被外卖线程给唤醒了,但是却没有拿到烟,这是小南线程还是没干活,这就是新出现的问题。

  • Step 4

我们之前使用的if判断,如果我们改成while循环就解决了上面问题

@Slf4j
public class jvm {
    static final Object room=new Object();
    static boolean hasCigarette=false;
    static boolean hasTakeout=false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有烟没?[{}]",hasCigarette);
                while(!hasCigarette){
                    try {
                        log.debug(("没烟,先歇一会!"));
                         room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("有烟没?[{}]",hasCigarette);
                if(hasCigarette){
                    log.debug("开始干活了!");
                }else {
                    log.debug("这活干不了!");
                }
            }
        },"小南").start();
        new Thread(()->{
            synchronized (room){
                log.debug("外买到了没?[{}]",hasTakeout);
                while(!hasTakeout){
                    try {
                        log.debug(("没外卖,先歇一会!"));
                        room.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("外买到了没?[{}]",hasTakeout);
                if(hasTakeout){
                    log.debug("开始干活了!");
                }else{
                    log.debug("这活干不了!");
                }
            }
        },"小北").start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout=true;
                log.debug("外卖到了哦!");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}

在这里插入图片描述

到此为止我们解决了虚假唤醒的问题

4. 总结

我们使用wait-notify的正确姿势应该如下:

synchronized(lock){
	while(条件B不成立)
	{
       lock.wait();
	}
	//代码逻辑
}
synchronized(lock){
	lock.notifyAll();
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1180310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新浪微博一键删除所有内容

亲自测试用 具体操作如下&#xff1a; 对应的 1 2 如下&#xff0c;进入这个界面是按F12 就可以看到 最后画横线的位置 替换自己的id 对应的就是 3 具体代码如下 //向删除接口发起请求&#xff0c;删除对应节点 function del_weibo(id) {var myHeaders new Headers();myHea…

小白学爬虫:通过商品ID获取1688跨境属性数据接口|1688商品属性接口|1688一件代发数据接口|1688商品详情接口

通过商品ID获取1688跨境属性数据接口可以使用1688开放平台提供的API接口实现。以下是获取跨境属性数据的基本步骤&#xff1a; 点击获取测试key和secret构造请求参数&#xff0c;包括商品ID和其他必要参数&#xff0c;如接口权限、请求类型等。通过API接口链接&#xff0c;将请…

App启动——Application的创建

Application的创建 一个 app 启动时候创建一个 Application 对象。这个对象的创建时间在 frameworks 中调用创建的&#xff0c;创建流程可见下图&#xff0c;涉及的几个主要的类调用。 一、主线程运行入口 ​ APP进程起来后&#xff0c;主线程运行入库 ActivityThread.main()…

Docker DeskTop的安装(Windows版本)

目录 一、官网下载Docker安装包 二、安装Docker DeskTop 2.1 双击 Docker Installer.exe 以运行安装程序 2.2 安装操作 2.3 关于更改Docker安装位置 2.3.1 自定义安装路径&#xff08;不推荐&#xff09; 2.3.2 移动 Docker 镜像存储位置 三、启动Docker DeskTop 一、官…

GaussDB SQL基础语法-变量常量

目录 一、前言 二、GaussDB数据库中的常量和变量的基本概述及语法定义 1、变量定义 2、常量定义 3、其他&#xff08;%TYPE、%ROWTYPE属性&#xff09; 三、在GaussDB数据库中如何使用变量&常量&#xff08;示例&#xff09; 示例一&#xff0c;定义常量&变量&a…

有什么软件可以管控员工的电脑桌面

信息化的快速发展&#xff0c;员工在工作中使用电脑的情况越来越普遍。然而&#xff0c;员工在使用电脑时可能会出现工作效率低下、滥用公司资源等问题&#xff0c;因此对员工电脑进行监测和管理显得尤为重要。 1、域之盾软件 它是一款功能强大的电脑监控软件&#xff0c;可以…

(二)Spring源码解析:默认标签解析

一、概述 还记得我们在上一讲末尾提到的关于默认标签解析和自定义标签解析吧。本讲就来针对默认标签解析进行讲解。为了便于衔接上一讲的内容&#xff0c;我们将源码部分粘贴出来&#xff1a; 从上图中的源码中&#xff0c;我们可以看出默认标签的解析是在parseDefaultElement…

Unit2_1:动态规划DP

文章目录 一、介绍二、0-1背包问题问题描述分析伪代码时间复杂度 三、钢条切割问题问题描述分析伪代码过程 四、矩阵链乘法背景性质分析案例伪代码 一、介绍 动态规划类似于分治法,它们都将一个问题划分为更小的子问题 最优子结构:问题的最优解包含子问题的最优解。DP适用的原…

一种单总线串口通信的调试方法

单总线的优点&#xff1a; 节省IO口&#xff0c;发送时可以将单片机的RXD设置为普通IO进行软件模拟发送&#xff0c;发送完设置为串口接收。避免通信干扰&#xff0c;由于是通过IO口对三极管/MOS管进行拉高拉低&#xff0c;外部信号不易对IO口进行干扰&#xff0c;EMI&#xf…

代码随想录算法训练营第四十六天 | LeetCode 139. 单词拆分、多重背包、背包总结

代码随想录算法训练营第四十六天 | LeetCode 139. 单词拆分、多重背包、背包总结 文章链接&#xff1a;单词拆分 多重背包 背包总结 视频链接&#xff1a;单词拆分 1. LeetCode 139. 单词拆分 1.1 思路 本题的那些单词就是物品&#xff0c;字符串就是背包&#xff0c;问用这些…

大数据学习之一文学会Spark【Spark知识点总结】

文章目录 什么是SparkSpark的特点Spark vs HadoopSparkHadoopSpark集群安装部署Spark集群安装部署StandaloneON YARN Spark的工作原理什么是RDDRDD的特点Spark架构相关进程Spark架构原理 Spark实战&#xff1a;单词统计Scala代码开发java代码开发任务提交 Transformation与Acti…

echarts 类目轴设置xAxis.interval不起效果

情景&#xff1a;在xAxis.typecategory&#xff1b;设置xAxis.interval不起效果&#xff1b; 解决方案&#xff1a;通过设置xAxis.axisLabel.interval属性

【H616_语言小美_控制安卓刷抖音项目 orangePi zero2 (已开源) 】.md uptada:23/11/07

文章目录 H616_语言小美_控制安卓刷抖音项目小美效果展示H616 ubuntu系统 安装adb智能公元 SU-03T 离线语音模组 固件制作配合串口实现 小美_控制安卓刷抖音 H616_语言小美_控制安卓刷抖音项目 注意&#xff1a;orangePi zero2 H616 安装系统为ubuntu 小美效果展示 语言小美 …

企业级私有化部署数字办公工具集合,解决企业全方位需求,快收藏

随着信息技术的日新月异&#xff0c;企业对IT建设的依赖程度越来越高。为了满足这一需求&#xff0c;软开企服凭借积累了多年的项目经验&#xff0c;推出了一款名为JVS的私有化部署企业信息化底座。JVS涵盖了多个产品领域&#xff0c;如协同办公、低代码开发、数据分析、风控决…

KaiOS APN配置文件apn.json调试验证方法(无需项目全编)

1、KaiOS 的应用就类似web应用&#xff0c;结合文件夹路径webapp字面意思理解。 2、KaiOS APN配置文件源代码在apn.json&#xff0c; &#xff08;1&#xff09;apn.json可以自定义路径&#xff0c;通过配置脚本实现拷贝APN在编译时动态选择路径在机器中生效。 &#xff08;…

linux复习笔记03(小滴课堂)

find命令&#xff1a; d查找目录&#xff1a; 按照文件权限查找&#xff1a; 600全部权限&#xff1a; -user根据所属主&#xff1a; 上面的例子是找出文件并打印有多少行。 我们也可以把我们查询到的结果复制到其它文件位置中去&#xff1a; 复制成功。 -mtime根据修改时间…

DOCTYPE是什么,有何作用、 使用方式、渲染模式、严格模式和怪异模式的区别?

前言 持续学习总结输出中&#xff0c;今天分享的是DOCTYPE是什么&#xff0c;有何作用、 使用方式、渲染模式、严格模式和怪异模式的区别。 DOCTYPE是什么&#xff0c;有何作用&#xff1f; DOCTYPE是HTML5的文档声明&#xff0c;通过它可以告诉浏览器&#xff0c;使用那个H…

MySQL的高阶语句

数据库的权限一般很小&#xff0c;工作中使用最多的场景就是查 排序、分组、子查询、视图、多表连接查询&#xff08;左连接、右连接、内连接&#xff09; create TABLE info ( id int(4) primary key, NAME varchar(5) not null, score decimal(5,2), address varchar(20)…

Django实战项目-学习任务系统-发送短信通知

接着上期代码内容&#xff0c;继续完善优化系统功能。 本次增加发送短信通知功能&#xff0c;学习任务系统发布的任务&#xff0c;为了更加及时通知到学生用户&#xff0c;再原有发送邮件通知基础上&#xff0c;再加上手机短信通知功能。 第一步&#xff1a;开通短信通知服务…

JSP通用材料收集归档系统eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 JSP 通用材料收集归档系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&#xff0c…