【JavaEE初阶 — 多线程】wait() notify()

    c96f743646e841f8bb30b2d242197f2f.gif

ddb5ae16fc92401ea95b48766cb03d96.jpeg692a78aa0ec843629a817408c97a8b84.gif

    1. 协调多个线程之间的执行先后顺序的方法介绍    


由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知;但是实际开发中,有时候我们希望合理地协调多个线程之间的执行先后顺序。

    拓展: wait() 和 sleep() 的区别     


wait() 和 sleep()都是用于暂停线程的操作,但它们有明显的区别(先说面试官最关心的):


    (1)使用要求不同    


  • wait() 必须在同步块或同步方法内调用(嵌套一层 synchronized ),否则会抛出IllegalMonitorStateException。
  • 这是因为 wait() 依赖于对象锁来管理线程的等待和唤醒机制。
  • 调用后,当前线程会释放它持有的对象锁,并进入等待状态。

  • sleep()方法可以在任何上下文中调用,不需要获取对象锁
  • 调用后,线程会进入休眠状态,但不会释放它持有的任何锁
  • 所以如果 wait() 和 sleep() 都嵌套一层锁,分别被唤醒时,wait() 会释放锁,而 sleep() 不会释放锁;

    (2)方法所属类不同    


  • wait()   :属于 Object 类的非静态方法。
  • sleep() :属于 Thread 类的静态方法。

    (3)恢复方式不同    


  • wait() 需要被其他线程通过 notify() 或 notifyAll() 显式唤醒;
  • 或者被 wait(long timeout) 的超时参数唤醒。

  • sleep() 在指定时间后自动恢复运行,或通过 interrupt() 提前唤醒,抛出 InterruptedException 异常

    (4)用途不同    


  • wait() 通常用于线程间通信,配合 notify() 或 notifyAll() 来实现线程的协调工作。

  • sleep() 用于让线程暂停执行一段时间,通常用于控制线程的执行频率或模拟延时

     (5)常见错误     


     误用sleep() :    

有时开发者会错误地使用 sleep() 进行线程间通信,但是 sleep() 不释放锁,可能会导致其他线程无法进入同步块,造成线程饥饿或死锁。


     忽略中断:    

sleep() 可能抛出 InterruptedException , 如果不正确处理中断信号,可能会导致线程提前退出或错误行为。


   2. wait()     


   2.1  线程饿死    

如上图,鸟妈妈(CPU)抓虫(调度资源)喂小鸟(线程),就是一个典型的 “线程饿死” 情景:

  • 对于线程饿死,并不是鸟妈妈把捉到的虫全都喂一只鸟宝宝,其他鸟宝宝一点都吃不到,而是鸟妈妈把捉到的虫子,绝大多数都喂给了一个鸟宝宝,剩下的鸟宝宝只能吃到一点点;
  • 使用 wait(),notify() 就是为了优化 “鸟妈妈把大多数的虫子,都分给一只鸟宝宝” 这一行为。

    2.2  调用 wait()     


  • wait() 和 notify 都是Object 的方法;Java 中的任意对象都提供了 wait() 和 notify();
  • wait() 能使当前执行代码的线程进行等待(把线程放到等待队列中);
  • wait() 一被调用,就会释放当前的锁;
  • 满足一定条件时被唤醒,重新尝试获取这个锁;
  • 注意:在判断是否满足唤醒条件时,我们可以把 if(判断条件)  改成 while(判断条件) ,这样可以避免被 interrupt() 类似的方法非法打断。在该文章模拟阻塞队列的 put() 和 take() 有详细解释 


  • wait 要搭配 synchronized 来使用;脱离synchronized,使用 wait 会直接抛出上述异常;
  • 上述异常被抛出的本质,是针对未加锁的锁对象进行释放锁操作


    2.3 唤醒 wait()    

  • 其他线程调用该对象的 notify() ;
  • wait() 等待时间超时(wait() 提供一个带有 timeout 参数的版本,来指定等待时间);
  • 其他线程调用该等待线程的 interrupted(),导致 wait() 抛出  InterruptedException 异常;

  • 在synchronized的代码块中,等到wait() 结束,wait后面到 }的部分,还有一些其他的逻辑;
  • 这些逻辑还是期望在锁的范围内进行调度,所以 wait 后面到}的部分也要嵌套在锁内,wait() 被唤醒后,会重新对这些逻辑上锁,以保证线程安全。

   3. notify()   


    3.1 wait() 和 notify() 的需要同一个对象调用    


  • 通过相同的对象调用 wait() 和 notify() ,是两个线程沟通的桥梁;
  • wait() 和 notify() 针对同一个对象才能生效;如果是不同对象,则没有任何相互的影响和作用~
  • 为了验证这一点,外面写出如下代码:

    代码逻辑:   

  • 在 t1 线程执行到第一个打印日志之后,执行 wait() ,此时就需要通过 t2 线程来唤醒 t1;
  • 我们先用 Scaner 来阻塞 t2,这样操作就可以手动控制 t2 对 t1 的唤醒;
  • 在输入内容后,t2执行 notify(),如果调用 wait() 和 notify() 的两个对象相同,则 t1 会成功被唤醒。

上图是不同对象调用 wait() 和 notify() 的情况,我们再来看看相同对象调用的结果:


    3.2  notify() 要同步方法或同步块中调用    


和 wait() 一样,也是要在同步方法或同步块(嵌套一层 synchronized)中调用;


    3.3 notify() 随机唤醒多个 wait() 中的其中一个    


  • notify()通知正在 wait(), 等待同一个对象锁的其它线程,对其它线程中的一个线程,发出通知notify,并使这个线程重新获取该对象的对象锁 
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈wait状态的线程。(并没有"先来后到")

  • 我们来看程序运行结果,在输入任意内容后,打印的结束日志是不一样的

  • 这就证明了线程调度器随机挑选出一个呈 wait 状态的线程
  • 有两个 wait(),一个 notify(),一定有一个线程没有被唤醒,导致整个进行无法结束;
  • 既然有两个 wait(),我们就设置两个 notify() 即可解决该问题;


  • 在 notify()方法后,当前线程不会马上释放该对象锁;
  • 要等到执行 notify() 所在同步代码块退出之后,才会释放对象锁 ;

   4. notifyAll()   


  •  对于刚刚上面写的代码,只有一个 notify(),就只能唤醒一个 wait():

  • 因此,我们可以考虑用 notifyAll(),唤醒所有相同对象调用 wait() 的线程:

  • 虽然同时唤醒了 t1 和 t2,但是由于 wait() 被唤醒之后,要重新加锁;因此其中某个线程,先加上锁,开始执行,而另一个线程因为加锁失败,再次阻塞等待;
  • 等到先加锁的线程解锁了,后加锁的线程才能加上锁,而继续执行~

    总结    

  • 因为这个原因,notifyAll() 在实际开发中,虽然可以唤醒所有 wait() ,但是用的并不多。
  • 因为不是一口气全部唤醒 wait(),而是每次唤醒其中一个线程,通过多次唤醒,把所有 wait()状态的线程唤醒;
  • notifyAll() 在唤醒其中一个 wait() 状态的线程时,其他线程依旧因为 wait() 尝试重新获取锁对象,而陷入阻塞等待;
  • 比起唤醒所有,我们更希望通过一个一个的 notify() 精确唤醒每一个线程。

     5. 应用 wait() 和 notify() 解决编程题    


    5.1 题目    


     5.2 程序运行结果     


    5.3 完整代码     

package Thread;public class Demo29 {public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Object locker3 = new Object();Thread t1 = new Thread(() -> {try {for (int i = 0; i < 10; i++) {synchronized (locker1) {locker1.wait();}System.out.print("A");synchronized (locker2){locker2.notify();}}} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t2 = new Thread(() -> {try {for (int i = 0; i < 10; i++) {synchronized (locker2) {locker2.wait();}System.out.print("B");synchronized (locker3){locker3.notify();}}} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t3 = new Thread(() -> {try {for (int i = 0; i < 10; i++) {synchronized (locker3) {locker3.wait();}System.out.println("C");synchronized (locker1){locker1.notify();}}} catch (InterruptedException e) {throw new RuntimeException(e);}});t1.start();t2.start();t3.start();//轮子已经造好了,现在需要推一把,让轮子转起来//需要确保上述主线程都执行到 wait(),再推轮子Thread.sleep(1000);synchronized (locker1){locker1.notify();}}
}

    c96f743646e841f8bb30b2d242197f2f.gif

692a78aa0ec843629a817408c97a8b84.gif

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

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

相关文章

Vscode/Code-server无网环境安装通义灵码

Date: 2024-11-18 参考材料&#xff1a;https://help.aliyun.com/zh/lingma/user-guide/individual-edition-login-tongyi-lingma?spma2c4g.11186623.0.i0 1. 首先在vscode/code-server插件市场中安装通义插件&#xff0c;这步就不细说了。如果服务器没网&#xff0c;会问你要…

Java项目实战II基于Java+Spring Boot+MySQL的共享汽车管理系统(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在共享经济…

美赛优秀论文阅读--2023C题

文章目录 1.题目说明2.我对于这个题目信息的理解3.优秀论文学习3.1摘要3.2相关的算法模型 4.总结 1.题目说明 今天阅读的这个文章来自于这个2023年的这个美赛的这个C题的论文&#xff1b; 我们的这个题目可以到网上去找&#xff0c;这个还是比较容易找到的&#xff0c;大致就…

ChromeDriver驱动下载地址更新(保持最新最全)

说明&#xff1a; ChromeDriver 是 Selenium WebDriver 用于控制 Chrome 的独立可执行文件。 为了方便下载使用&#xff0c;本文保持ChromeDriver的最新版本更新&#xff0c;并提供115.0.5763.0-133.0.6841.0版本的下载地址&#xff1a; 所有版本和下载地址&#xff1a; &am…

delphi fmx android 离线人脸识别

搜遍全网都没有找到delphi android 能用的 离线人脸识别,无需注册什么开发者 有这方面需求的可以用fsdk 这边用的luxand.FSDK8.0 android下的注册号要自己找下 1,用老猫的工具将android 下的sdk,FSDK.java 编译成FSDK.jar 老猫的工具 2,用上面的工具将FSDK.jar 生成de…

【模块一】kubernetes容器编排进阶实战资源对象之Configmap与Secret简介

kubernetes 资源对象详解及示例 kubernetes 的几个重要概念 资源对象&#xff1a;kubernetes基于声明式API&#xff0c;和资源对象进行交互。 yaml文件&#xff1a;为了方便后期管理&#xff0c;通过使用yaml文件通过API管理资源对象。 yaml必需字段&#xff1a; apiVersio…

游戏引擎学习第14天

视频参考:https://www.bilibili.com/video/BV1iNUeYEEj4/ 1. 为什么关注内存管理&#xff1f; 内存分配是潜在的失败点&#xff1a; 每次进行内存分配&#xff08;malloc、new等&#xff09;时&#xff0c;都可能失败&#xff08;例如内存不足&#xff09;。这种失败会引入不稳…

游戏引擎学习第12天

视频参考:https://www.bilibili.com/video/BV1yom9YnEWY 这节没讲什么东西&#xff0c;主要是改了一下音频的代码 后面有介绍一些alloc 和malloc,VirtualAlloc 的东西 _alloca 函数&#xff08;或 alloca&#xff09;分配的是栈内存&#xff0c;它的特点是&#xff1a; 生命周…

django——创建 Django 项目和 APP

2.创建 Django 项目和 APP 命令&#xff1a; 创建Django项目 django-admin startproject name 创建子应用 python manager.py startapp name 2.1 创建工程 在使用Flask框架时&#xff0c;项目工程目录的组织与创建是需要我们自己手动创建完成的。 在django中&#xff0c;…

OceanBase 分区表详解

1、分区表的定义 在OceanBase数据库中&#xff0c;普通的表数据可以根据预设的规则被分割并存储到不同的数据区块中&#xff0c;同一区块的数据是在一个物理存储上。这样被分区块的表被称为分区表&#xff0c;而其中的每一个独立的数据区块则被称为一个分区。 如下图所示&…

学习大数据DAY61 宽表加工

目录 模型设计 加工宽表 任务调度&#xff1a; 大表 - 把很多数据整合起来 方便后续的明细查询和指标计算 模型设计 设计 建模 设计: excel 文档去编写 建模: 使用建模工具 PowerDesigner Navicat 在线画图工具... 把表结构给绘 制出来 共享\项目课工具\pd 加工宽表 数…

uniapp微信小程序转发跳转指定页面

onShareAppMessage 是微信小程序中的一个重要函数&#xff0c;用于自定义转发内容。当用户点击右上角的菜单按钮&#xff0c;并选择“转发”时&#xff0c;会触发这个函数。开发者可以在这个函数中返回一个对象&#xff0c;用于定义分享卡片的标题、图片、路径等信息。 使用场…

Datawhale组队学习】模型减肥秘籍:模型压缩技术3——模型量化

模型量化的目的是通过将浮点运算转换为定点运算&#xff0c;以减少模型大小、内存和存储需求&#xff0c;同时加快推理速度&#xff0c;降低计算功耗&#xff0c;使得模型在低算力设备上运行更加高效&#xff0c;特别适用于嵌入式设备或移动端场景。 不同数据类型&#xff08;…

arkUI:网格布局(Grid)

arkUI&#xff1a;网格布局&#xff08;Grid&#xff09; 1 主要内容说明2 网格布局的相关内容2.1 基本使用2.1.1 源码1 &#xff08;Grid网格布局&#xff09;2.1.2 源码1运行效果 2.2 设置排列方式2.2.1 核心布局参数2.2.2 网格单元格参数&#xff08;GridItem&#xff09;2.…

Cherno OpenGL(28 ~ 33)

批量渲染-介绍 在这里我们将在一个drawcall打包多个几何体。即 batch geometry。 我们在这里将聚焦于2d渲染&#xff0c;我们如何渲染一堆2d的quads或者说rectangles呢&#xff1f; 一种情况是比如一个2d游戏有很多个tile组成&#xff0c;要去渲染这些tile&#xff1b;另一种…

网络安全之国际主流网络安全架构模型

目前&#xff0c;国际主流的网络安全架构模型主要有&#xff1a; ● 信息技术咨询公司Gartner的ASA&#xff08;Adaptive Security Architecture自适应安全架构&#xff09; ● 美国政府资助的非营利研究机构MITRE的ATT&CK&#xff08;Adversarial Tactics Techniques &…

游戏引擎学习第16天

视频参考:https://www.bilibili.com/video/BV1mEUCY8EiC/ 这些字幕讨论了编译器警告的概念以及如何在编译过程中启用和处理警告。以下是字幕的内容摘要&#xff1a; 警告的定义&#xff1a;警告是编译器用来告诉你某些地方可能存在问题&#xff0c;尽管编译器不强制要求你修复…

【Nginx从入门到精通】03 、安装部署-让虚拟机可以联网

文章目录 总结一、配置联网【Minimal 精简版】1.1、查看网络配置1.2、配置ip地址 : 修改配置文件 <font colororange>ifcfg-ens33Stage 1&#xff1a;输入指令Stage 2&#xff1a;修改参数Stage 3&#xff1a;重启网络Stage 4&#xff1a;测试上网 二、配置联网【Everyth…

【UGUI】背包的交互01(道具信息跟随鼠标+道具信息面板显示)

详细程序逻辑过程 初始化物品栏&#xff1a; 在 Awake 方法中&#xff0c;通过标签找到提示框和信息面板。 循环生成10个背包格子&#xff0c;并为每个格子设置图标和名称。 为每个格子添加 UInterMaager232 脚本&#xff0c;以便处理交互事件。 关闭提示框和信息面板&#…

MySQL45讲 第二十四讲 MySQL是怎么保证主备一致的?——阅读总结

文章目录 MySQL45讲 第二十四讲 MySQL是怎么保证主备一致的&#xff1f;——阅读总结一、MySQL 主备基本原理&#xff08;一&#xff09;主备切换流程&#xff08;二&#xff09;主备数据同步流程 二、binlog 格式及相关问题&#xff08;一&#xff09;binlog 的三种格式&#…