2023.10.17 关于 wait 和 notify 的使用

目录

引言

方法的使用

引入实例(wait 不带参数版本)

wait 方法执行流程

wait 和 notify 组合实例

wait 带参数版本

notify 和 notifyAll 的区别

经典例题 

总结 


引言

  • 线程最大的问题是抢占式执行,随机调度
  • 虽然线程在内核里的调度是随机的,但是可以通过一些api 来控制线程之间的执行顺序,让线程主动阻塞,主动放弃CPU,以便让给其他线程使用

简单示例

  • 有线程t1 和 线程t2,希望线程t1 先干活,等到干的差不多了,再让线程t2 来干活
  • 此时就可以让线程t2 先 wait(阻塞,主动放弃CPU),等线程t1 干的差不多了,再通过 notify 通知线程t2,把线程t2 唤醒,让线程t2 接着干
注意:

join 或 sleep 与 wait 和 notify 之间使用的区别

  • 使用 join ,则表示线程t1 必须要彻底执行完,线程t2 才能运行,如果希望线程t1 先干 一半活,再让线程t2 接着干,此时 join 就不能满足需求
  • 使用 sleep 来指定一个休眠时间,同理我们难以知道线程t1 干一半活所需的具体时间,所以难以满足我们的需求
  • 可以认为 wait 和 notify 涵盖了 join 的用途,但是 wait 和 notify 的使用要比 join 麻烦很多,可以根据实际使用场景来自行选择

方法的使用

引入实例(wait 不带参数版本)

public class ThreadDemo16 {public static void main(String[] args) throws InterruptedException {Object object = new Object();
//        wait 不加任何参数,就是死等,一直等待,直到有其他线程唤醒它object.wait();}
}

执行结果:

  • 该异常为非法的锁状态异常
  • 锁状态就两种一种是 加锁状态,一直是 解锁状态
  • 关于为啥会出现异常,我们还需了解 wait 方法的执行流程

wait 方法执行流程

  • 先释放锁
  • 进行阻塞等待
  • 收到通知之后,重新尝试获取锁,并且在获取锁后,继续往下执行

修改实例

public class ThreadDemo16 {public static void main(String[] args) throws InterruptedException {Object object = new Object();
//        wait 不加任何参数,就是死等,一直等待,直到有其他线程唤醒它synchronized (object) {System.out.println("wait 之前");object.wait();System.out.println("wait 之后");}}
}

运行结果:

  • 相较于 object 未加锁就调用 wait 方法,而出现锁状态异常的报错
  • 这里先给 object 加上锁,再调用 wait 方法,就能很好的进行阻塞等待,并处于 WAITING 状态
  • 此处阻塞 释放掉了对象 object 的锁,从而其他线程 便可以获取对象 object 的锁

wait 和 notify 组合实例

package Thread;public class ThreadDemo17 {public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread t1 = new Thread(() -> {
//            这个线程负责进行等待System.out.println("t1: wait 之前");synchronized (object) {try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1: wait 之后");});Thread t2 = new Thread(() -> {
//            这个线程负责唤醒System.out.println("t2: notify 之前");synchronized (object) {
//                notify 务必要获取到锁,才能进行通知object.notify();}System.out.println("t2: notify 之后");});t1.start();
//        这里添加 sleep 等待1秒
//        是为了能够尽量保证先执行线程t1 再执行线程t2Thread.sleep(1000);t2.start();}
}

运行结果:

注意:

  • 此处的 notify 需和 wait 进行配对
  • 如果 wait 使用的对象和 notify 使用的对象不一致
  • 此时 notify 将不会有任何效果
  • 因为 notify 只能唤醒在同一个对象上等待的线程

  • 虽然这里的代码顺序是先执行线程t1 再执行线程t2
  • 但是由于线程调度的随机性,并不能完全保证一定是先执行线程t1 再执行线程t2 
  • 如果调用 notify 时没有线程在 wait,此时的 wait 是无法被唤醒的那么这种通知就是无效通知,但不会有啥副作用
  • 所以在执行线程t1 之后,先 sleep 等待1秒,再执行线程t2,能很大程度的保证线程t1 先执行 wait 方法

wait 带参数版本

  • 上述代码的 wait 为无参数版本,意味着只要线程t2 不进行 notify,此时线程t1 就会始终 wait 下去,也就是死等
  • 所以 wait 带参数版本便能指定一个等待的最大时间, 能很好的避免死等情况的出现

注意:

  • 虽然 wait 带参数版本,看起来跟 sleep 有点像
  • 都能指定等待时间
  • 都能被提前唤醒,wait 使用 notify ,sleep 使用 interrupt
  • 但是还是有本质差别的,其含义截然不同
  • notify 唤醒 wait,是正常的业务逻辑,并不会有任何异常
  • interrupt 唤醒 sleep 则会先触发中断异常,表示这是一个出了问题的逻辑

notify 和 notifyAll 的区别

  • 当有多个线程等待 object 对象时
  • 有一个线程执行 notify 方法,那么将会随机唤醒一个等待的线程
  • 有一个线程执行 notifyAll 方法,那么将唤醒全部等待的线程,然后这些线程再一起竞争锁

经典例题 

  • 有三个线程,分别只能打印 A、B、C ,控制三个线程固定按照 ABC 的顺序来打印

具体思路

  • 我们可以创建两个对象 object1 和 object2
  • object1 用来控制线程t1和线程t2 的执行顺序
  • object2 用来控制线程t2和线程t3 的执行顺序
  • 让线程t3 wait 阻塞等待对象 object2,直到线程t2 执行 notify 
  • 让线程t2 wait 阻塞等待对象 object1,直到线程t1 执行 notify 
  • 这样便能很好的保证先执行线程t1 ,再执行线程t2,最后再执行线程t3
public class ThreadDemo18 {public static void main(String[] args) throws InterruptedException {Object object1 = new Object();Object object2 = new Object();Thread t1 = new Thread(() -> {System.out.println("A");synchronized (object1) {object1.notify();}});Thread t2 = new Thread(() -> {synchronized (object1) {try {object1.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("B");synchronized (object2) {object2.notify();}});Thread t3 = new Thread(() -> {synchronized (object2) {try {object2.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("C");});t2.start();t3.start();Thread.sleep(500);t1.start();}
}

运行结果:

注意:

  • 之所以这样安排线程的执行顺序并在执行线程t1 前先等待 0.5秒
  • 是因为能很大程度上避免线程t1 执行 notify 之后,线程t2 还未执行 wait 方法阻塞等待对象 object1 
  • 从而导致线程t1 notify 了个寂寞,便会导致线程t2 一直阻塞等待,出现死锁的情况

总结 

  • wait 和 notify 这两个 api 是用来控制线程之间执行顺序的
  • wait 和 notify 均属于 Object 类的方法

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

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

相关文章

【前端学习】—JS判断数据类型的方式有哪些(八)

【前端学习】—JS判断数据类型的方式有哪些(八) 一、JS中判断数据类型的场景 二、JS中有哪些数据类型 三、JS判断数据类型的方式有哪些 const arr[]; const object{};const number1; const stringstring;//typeofconst typetypeof arr; console.log(type…

从头开始机器学习:神经网络

一、说明 如果你还没有做过逻辑回归,你会在这里挣扎。我强烈建议在开始之前查看它。您在逻辑回归方面的能力将影响您学习神经网络的难易程度和速度。 二、神经网络简介 神经网络是一个神经元网络。这些神经元是逻辑回归函数,它们被链接在一起形成一个网络…

只会Python,怎么用PC控制无人机自动飞行?

PC-SDK是阿木实验室 (AMOVLAB) 为了简化开源飞控的控制协议MAVLink,优化和维护的一个基于PC电脑运行MAVSDK(支持Windows和Ubuntu)的Python SDK库。 相对于传统的无人机控制开发,开发者无需掌握C/C语言和ROS等相关知识,只要学会Python编程及懂…

leetcode 1143. 最长公共子序列、1035. 不相交的线、53. 最大子数组和

1143. 最长公共子序列 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些…

wsl使用vscode连接,远程安装C/C++ 拓展时,报错

报错内容: EACCES: permission denied, rename /home/wen/.vscode-server/extensions/.b61b1c7c-f703-4dfd-bdc5-d9a00681c4b7 -> /home/wen/.vscode-server/extensions/ms-vscode.cpptools-1.17.5-linux-x64 解决办法: 升级wsl到wsl2就好了。 &a…

Vue-router快速入门 是什么 如何跳转 如何传值的问题

3.1 Vue-router是什么 Vue-router:Vue.js 的官方路由为 Vue.js 提供富有表现力、可配置的、方便的路由 官网:https://router.vuejs.org/zh/ 作用: 1.实现vue页面(组件)的跳转 2.可以在跳转的时候携带参数 3.2 Vue3使用Vue-router(静态路由) 基于Vu…

C# CodeFormer Inpainting 人脸填充

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms;namespace CodeFormer_D…

UnitTesting 单元测试

1. 测试分为两种及详细介绍测试书籍: 1.1 Unit Test : 单元测试 - test the business logic in your app : 测试应用中的业务逻辑 1.2 UI Test : 界面测试 - test the UI of your app : 测试应用中的界面 1.3 测试书籍网址:《Testing Swift》 https://www.hackingwithswift.c…

MySQL——六、库表操作(下篇)

MySQL 一、INSERT语句二、REPLACE语句三、UPDATE语句四、delete和TRUNCATE语句五、MySQL用户授权1、密码策略2、用户授权和撤销授权 一、INSERT语句 #在表里面插入数据:默认情况下,一次插入操作只插入一行 方式1: INSERT [INTO] 表名 [(colu…

uni-app小程序使用DCloud(插件市场)流程

一、DCloud(插件市场) DCloud 是uni-app官方插件市场,里面有官方、团队、个人发布的众多插件,包括uni-ui、uni-pay 等。而像uni-ui这种大型组件库都有官方文档可参考,但一些团队或个人发布的小型插件没有文档&#xf…

垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程

文章目录 🍊 垃圾回收器、垃圾回收算法、空间分配担保🎉 Serial🎉 ParNew🎉 Parallel scavenge🎉 复制算法🎉 分代收集算法🎉 进入老年代的几种情况📝 空间分配担保 🎉 S…

超火的双臂烹饪机器人Project YORI,分分钟成为你的专属大厨!

原创 | 文 BFT机器人 当前行业内有两种通用的烹饪自动化方法:一种是“制造一个可以在普通厨房中运作的烹饪机器人,因为每个人都有厨房”,这听起来很不错,但接下来你就必须使你的烹饪机器人能够在厨房环境中正常运行,这…

docker 复习

文章目录 1. docker 基础1.1 docker 安装配置镜像加速器拉取镜像的仓库: docker 部署Mysql 镜像docker 命令的详细解释docker 常见命令docker 数据卷docker 相关命令总结 2.自定义镜像2.1 dockerfile2.2 try 构建一个Java镜像,并部署2.3 总结: 3. docker…

物流监管:智慧仓储数据可视化监控平台

随着市场竞争加剧和市场需求的不断提高,企业亟需更加高效、智能且可靠的仓储物流管理方式,以提升企业的物流效率,减少其输出成本,有效应对市场上的变化和挑战。 图扑自研 HT for Web 产品搭建的 2D 智慧仓储可视化平台&#xff0c…

Databend 开源周报第 115 期

Databend 是一款现代云数仓。专为弹性和高效设计,为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务:https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展,遇到更贴近你心意的 Databend 。 聚合索引 Data…

[计算机提升] 系统及用户操作

1.4 系统及用户操作 1.4.1 系统操作 1.4.1.1 开机、关机、重启 在Windows系统中,开机(Power On),关机(Shutdown)和重启(Restart)是指计算机的不同电源控制操作。 开机:…

苹果平板可以用别的电容笔吗?电容笔和Apple pencil区别

和苹果原装的Pencil相比,这种平替的电容笔并没具备重力压感,只有一种倾斜的压感功能。如果你不经常用来作画,一支普通的电容笔就足够了。不管是用来记笔记,还是用来解决一些数学问题,都能用得上。再说了,即…

NAND存储器转储分析 - 使用ECC修复位错误与UBI镜像固件分析

一、 简介 这篇研究论文将通过黑客的视角,详细阐述如何操作 NAND dump 以及如何获取 dump 文件中的所有文件。每一步骤以及所使用的方法均会细致解析,并配以实例说明。本文主要关注的是物理 NAND dump,这是从通用编程器中提取出的 dump 文件…

项目平台——测试报告的实现(七)

这里写目录标题 一、Table表格组件的使用1、Table表格组件中的插槽使用 二、点击查看测试报告,跳转到测试报告详情页实现1、新建Report.vue组件2、配置路由3、查看报告按钮添加事件 三、页面布局1、Layout布局2、卡片设计3、打开页面发送请求加载报告数据4、对接口进…

Java版本+企业电子招投标系统源代码+支持二开+招投标系统+中小型企业采购供应商招投标平台

功能模块: 待办消息,招标公告,中标公告,信息发布 描述: 全过程数字化采购管理,打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力,为外部供…