多线程——线程的等待通知
目录
·前言
一、wait() 方法
1.方法介绍
2.代码示例
3.wait 和 sleep 的区别
二、notify() 方法
1.方法介绍
2.代码示例
三、notifyAll() 方法
1.方法介绍
2.代码示例
·结尾
·前言
由于线程之间是抢占式执行的,因此线程之间的执行顺序是难以预知的,但是在我们进行多线程编程中,很多时候我们希望能合理的协调多个线程之间的执行先后顺序,本篇文章就是来进行介绍协调多个线程之间的执行先后顺序的方法,那就是使用wait() 方法、notify() 方法和notifyAll() 方法。
一、wait() 方法
1.方法介绍
wait 方法和 join 方法用法和作用相似,都可以协调多个线程之间的执行先后顺序,但是使用 join 方法是要等待另一个线程执行完,才能继续执行,使用 wait 方法则是等待另一个线程通过 notify 方法来通知就可以继续执行,wait 方法做的事情有以下几点:
- 使当前执行代码的线程进行等待(把线程放入等待队列中);
- 释放当前的锁;
- 满足一定条件时被唤醒,再重新尝试获取这个锁。
wait 方法结束等待的条件有以下几条:
- 其他线程调用该对象的 notify 方法;
- wait 等待时间超时(wait 方法有一个带有 timeout 参数的版本,可以指定等待的时间) ,避免出现死等的情况;
- 其他线程调用该线程的 interrupted 方法,导致 wait 方法抛出异常。
2.代码示例
wait 方法使用时一定是要搭配 synchronized 关键字来使用,如果脱离 synchronized 使用 wait 方法就会直接抛出异常,如下面代码及运行结果所示:
public class WaitDeom {public static void main(String[] args) throws InterruptedException {Object object = new Object();System.out.println(" wait 之前....");object.wait();System.out.println(" wait 之后....");}
}
上述代码中是直接调用了 wait 方法,此时出现的异常名称是“非法的监视器异常”,synchronized 也叫做“监视器锁”,锁对象,就像“监控”一样,一调用 wait 方法就需要释放锁,但是释放锁的前提是得先拿到锁,所以 wait 方法必须方法 synchronized 中使用。
介绍完 wait 方法为什么一定要在 synchronized 中使用后,我来对上述有问题的代码进行修改,修改后的代码及运行结果如下所示:
public class WaitDeom {public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println(" wait 之前....");object.wait();System.out.println(" wait 之后....");}}
}

我们可以发现,上面的代码在 wait 方法这里阻塞了,此时我们使用 jconsole 工具来观察线程状态,如下图所示:
调用完 wait 方法之后程序就会一直等待下去(这里调用的 wait 是没有带 timeout 参数的版本,所以会进行死等),但是我们不希望这个程序一直这么等待下去,此时就需要使用另一个方法来唤醒线程,那就是 notify() 方法。
3.wait 和 sleep 的区别
wait 方法和 sleep 方法的效果有一些相似之处,在使用方法上也有一些相似之处,先介绍一下这两个方法类似的地方:
在 wait 方法中,提供了一个带有超时时间的版本,sleep 方法也可以指定时间,他们都是时间到,就可以继续执行解除阻塞了,并且,wait 和 sleep 都可以被提前唤醒(虽然时间没有到,但是可以被提前唤醒)wait 通过 notify 来唤醒,sleep 通过 interrupt 唤醒。
在介绍这两个方法的区别之前,还是要强调一下,理论上这两个方法并没有可比性,这是因为 wait 方法是用于线程之间的通信,sleep 方法是让线程阻塞一段时间,唯一相同的就是这两个方法都可以让线程放弃执行一段时间,所以上面类似的地方,也仅仅是类似。
使用 wait 时一定是不知道要等多长时间的前提下使用,所以他带有超时时间的版本就是为了“兜底”,大多数情况下 wait 方法都是在超时时间之内被唤醒了。
使用 sleep 方法时一定是知道要等待多少时间的前提下使用的,虽然可以被提前唤醒,但是这并非一个正常的操作,因为在使用 sleep 方法时,我们就是希望这个线程在指定时间到达后被准时唤醒,而非被异常唤醒。
介绍这两个方法的区别最明显的还是以下两点:
- wait 方法需要搭配 synchronized 来使用,而使用 sleep 方法不需要;
- wait 方法是 Object 类中的方法,而 sleep 方法是 Thread 的静态方法。
二、notify() 方法
1.方法介绍
notify() 方法的作用是唤醒等待的线程,它有以下几点注意事项:
- notify() 方法需要在 synchronized 的代码块或用 synchronized 修饰的方法中进行使用,notify() 方法是用来通知那些可能等待该锁对象的其他线程,对其他线程发出通知 notify 并使它们重新获取锁对象;
- 调用 notify 方法时,如果有多个线程等待,就会有线程调度器随机调度出一个 wait 状态的线程,并不是按照“先来后到”的顺序进行调度;
- 在调用 notify 方法后,当前的线程不会立刻释放当前的锁对象,而是需要等当前调用 notify 方法的线程将当前 synchronized 代码块中代码执行完之后才会释放锁对象。
再强调一下,使用 notify 方法时一定要注意 wait 方法和 notify 方法彼此是通过锁对象来联系起来的,比如当前有两个锁对象:object1 与 object2 ,然后执行了 object1.wait() , object2.notify() 此时执行的 notify 方法就无法唤醒!只有这两个对象一样才能唤醒。
2.代码示例
通过上面对 notify 方法的介绍,在这里还是用具体的代码示例来演示一下吧,代码及运行结果如下所示:
// 测试 notify() 方法功能
public class NotifyDemo {public static void main(String[] args) {// 创建一个锁对象Object object = new Object();Thread t1 = new Thread(()->{synchronized (object) {System.out.println(" wait 之前");try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(" wait 之后");}});Thread t2 = new Thread(()->{// 让 t2 线程先休眠 2s 是为了让 t1 线程先执行到 wait() 方法try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (object){System.out.println("使用 notify 唤醒 wait");object.notify();// 这里休眠两秒是为了观察调用完 notify() 方法后会不会立即释放锁对象System.out.println("休眠 2 秒观察");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 启动两个线程t1.start();t2.start();}
}
上述代码就可以很好的演示了 notify() 方法的作用。
三、notifyAll() 方法
1.方法介绍
在上面介绍了 notify() 方法只是唤醒调用这个对象上诸多等待线程中的某一个线程,使用 notifyAll() 方法则是可以唤醒这个对象上所有等待的线程,假设我们创建的很多线程,这些线程都使用了同一个对象调用了 wait 方法,针对这个对象调用 notifyAll 方法就会将这些线程全部唤醒。但是需要注意,这些线程在 wait 状态被唤醒后是需要重新获取锁的,就会产生锁竞争,此时哪个线程先拿到锁,哪个线程后拿到锁就不确定了。
2.代码示例
介绍完 notifyAll() 方法之后,还是以一个代码示例来演示一下 notifyAll() 的具体效果吧,代码及运行结果如下所示:
// 测试 notifyAll() 方法
public class NotifyAllDemo {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(()->{synchronized (locker) {System.out.println("执行线程 t1 的 wait 方法前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1 线程被唤醒");}});Thread t2 = new Thread(()->{synchronized (locker) {System.out.println("执行线程 t2 的 wait 方法前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2 线程被唤醒");}});Thread t3 = new Thread(()->{synchronized (locker) {System.out.println("执行线程 t3 的 wait 方法前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t3 线程被唤醒");}});Thread t4 = new Thread(()->{// 休眠 t4 线程 3s 让前三个线程先执行完 wait 方法try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker) {System.out.println("唤醒全部线程.....");locker.notifyAll();}});// 启动所有线程,观察结果t1.start();t2.start();t3.start();t4.start();}
}

通过上面代码运行结果可以发现,两次运行结果并不相同,这是因为将所有调用 wait 方法进行等待的线程唤醒之后,它们会重新进行锁竞争,此时无法保证哪个线程先拿到锁,然后先执行,所以相比之下,我个人通知线程醒来还是更倾向用 notify() 方法,因为使用 notifyAll() 方法将所有等待线程唤醒后不好控制。
·结尾
文章到此也就要结束了,本篇文章介绍了线程的等待通知,等待使用 wait() 方法,通知使用的是 notify() 方法和 notifyAll() 方法,这样配合的一组方法可以让我们更合理的协调多个线程之间的执行顺序,这也是我们使用 Java 进行多线程编程中的基础知识,如果本篇文章对您有帮助,希望能得到您的三连支持,同样,如果对本篇文章的知识有所疑惑,也可以在评论区或者私信留言哦,您的支持是我最大的动力,我们下篇文章再见吧~~~
相关文章:
多线程——线程的等待通知
目录 前言 一、wait() 方法 1.方法介绍 2.代码示例 3.wait 和 sleep 的区别 二、notify() 方法 1.方法介绍 2.代码示例 三、notifyAll() 方法 1.方法介绍 2.代码示例 结尾 前言 由于线程之间是抢占式执行的,因此线程之间的执行顺序是难以预知的…...
模态与非模态的对话框
本文学习自: 《Qt Creato快速入门》 #include "widget.h" #include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); }1. #include "widget.h" #include "ui_w…...
C语言练习
题目: 1.运用switch选择语句,编写一段C语言,请根据输入的数字,显示相应的星期日,如果数字所对应的星期日并不存在请显示“抱歉,您输入的内容并不存在。” 分析:1.在本题中,要运用到…...
CyberRt实践之Hello Apollo(Apollo 9.0版本)
apollo9.0环境安装参考官方网站 apollo.baidu.com/community/Apollo-Homepage-Document?docBYFxAcGcC4HpYIbgPYBtXIHQCMEEsATAV0wGNkBbWA5UyRFdZWVBEAU0hFgoIH0adPgCY%2BADwCiAVnEAhAILiAnABZxEgOzK1Y%2BQA51M3ROUnJBsbK2WZoyUdkBhcXoAMhlwDFlARnUXZdzE9AGY%2BbFINADYpUhCEFW…...
【JavaScript】LeetCode:61-65
文章目录 61 课程表62 实现Trie(前缀树)63 全排列64 子集65 电话号码的字母组合 61 课程表 Map BFS拓扑排序:将有向无环图转为线性顺序。遍历prerequisites:1. 数组记录每个节点的入度,2. 哈希表记录依赖关系。n 6&a…...
【SpringAI】(一)从实际场景入门大模型——适合Java宝宝的大模型应用开发
一、简单场景介绍 假设你需要为一个商城项目接入一个基于SpringAI的智能客服系统,现在我们来基本模拟一下: 当我通过系统提问,大模型会针对我的问题进行回答。 当我们通过程序提问时,SpringAI会将我们的提问封装成Prompts&#x…...
植物大战僵尸杂交版
最新版植物大战僵尸杂交版 最近本款游戏火爆 下载资源如下: win版本:2.3.7 链接:下载地址 提取码:9N3P Mac(苹果版本):2.0.0 链接:下载地址 提取码:Bjaa 介绍ÿ…...
live2d 实时虚拟数字人形象页面显示,对接大模型
live2dSpeek 测试不用gpu可以正常运行 https://github.com/lyz1810/live2dSpeek 运行的话还需要额外下载https://github.com/lyz1810/edge-tts支持语音 ## 运行live2dSpeek >npm install -g http-server >http-server . ## 运行edge-tts python edge-tts.py...
SpringCloud-持久层框架MyBatis Plus的使用与原理详解
在现代微服务架构中,SpringCloud 是一个非常流行的解决方案。而在数据库操作层面,MyBatis Plus 作为 MyBatis 的增强工具,能够简化开发,提升效率,特别是在开发企业级应用和分布式系统时尤为有用。本文将详细介绍 MyBat…...
Servlet的HttpServletRequest
HttpServletRequest是Java Servlet规范中定义的一个接口,它表示客户端向服务器发送的请求,并提供了与HTTP请求相关的方法和属性。 getSession方法():用于获取与当前请求相关联的HttpSession对象。 setAttribute(String name, Object value)…...
U9销售订单不能带出最新价格出来
业务员突然说系统带不出来销售价格。了解之后,不是带不出来价格,是做了价格调整之后,最新价格没有匹配出来,带出来的价格是历史价格。检查,分析相关的单据,生效日期,失效日期,审核状…...
Jmeter接口测试企业级项目实战day1
1.接口测试 接口测试工具: JMeter:支持多种接口类型,还能测试性能,开源,开源进行二次扩展。 Postman:简单,方便,局限性比较大,适合开发临时行调试 APIFox等:新…...
接口测试面试题含答案
1、解释一下正向和逆向测试。 正向测试:针对接口设计预期的功能和行为,验证接口是否按照预期工作。 逆向测试:针对错误输入、不合理的条件或非预期的使用方式,验证接口是否能够适当地处理这些情况并提供合理的错误处理。 2、什…...
横板营业执照提取生成
前言 有一段时间没发博客了,今天分享下几个月前做的营业执照提取器UI 预览图 框架 b-ui很好用,这个前端框架作者 发布的插件我都会用,鱿鱼助手也是基于这个框架开发的 代码 html <template><view><template><view…...
webm格式怎么转换成mp4?这5种转换方法很好用
现如今,视频格式繁多,而webm作为一种由谷歌开发的视频格式,以其高画质和低带宽需求著称。然而,并非所有设备和播放器都完美支持webm格式,这时将其转换为兼容性更强的MP4格式就显得尤为重要。下面给大家分享5种非常简单…...
C/C++语言基础--C++异常看这一篇就够了
本专栏目的 更新C/C的基础语法,包括C的一些新特性 前言 通过前面几节课,我们学习了抽象、封装、继承、多态等相关的概念,接下来我们将讲解异常,异常是专门处理错误的;这一次加了不少图标,希望大家喜欢;C语…...
DFT ATPG中常见影响coverage的因素有哪些?
# DFT ATPG中常见影响Coverage的因素 ## 一、电路结构复杂性 1. **逻辑层次深度** - **原理** - 当电路的逻辑层次很深时,信号在传播过程中会经过多个逻辑门的处理。这使得测试向量难以准确地控制和观察内部节点的状态。例如,在一个具有多层嵌套逻辑的电路中,如一个…...
Python机器学习数据清洗到特征工程策略
Python机器学习数据清洗到特征工程策略 目录 ✨ 数据清洗:处理缺失值与异常值的策略🔄 特征选择:筛选与数据目标高度相关的特征🛠 特征工程:数据转换与生成新特征的多样化方法📊 类别型变量的数值化&…...
多线程-进阶(2)CountDownLatchConcurrentHashMapSemaphore
目的; JUC(java.util.concurrent) 的常⻅类 接着上一节课到 1.信号量 Semaphore 信号量, ⽤来表⽰ "可⽤资源的个数". 本质上就是⼀个计数器。 理解信号量 可以把信号量想象成是停⻋场的展⽰牌: 当前有⻋位 100 个. 表⽰有 100 个可⽤资源. 当有⻋开进去的时候,…...
密码管理器KeePass的安装及使用
文章目录 软件下载安装汉化新建数据库创建\移动\修改 群组添加/修改/删除/移动 记录展示、搜索、锁定单独使用keepass生成密码的功能AES-256的密钥长度为256位,为啥可以设置超过32个字符的密钥? 软件下载 安装 分别解压:KeePass-2.53.1.zip&…...
三星 Infinite AI 葡萄酒冰箱:智能厨房新尝试能否突围?
AI 加持,葡萄酒管理新体验周一,三星推出了 Infinite AI 葡萄酒冰箱,目前仅在韩国有售。这款冰箱采用了“AI 葡萄酒管理器”,借助安装在顶部的“AI 视觉”摄像头,能检测用户放入或取出的酒瓶及位置,还能分析…...
OFA-COCO蒸馏版部署教程:Windows WSL2环境下PyTorch服务调试全流程
OFA-COCO蒸馏版部署教程:Windows WSL2环境下PyTorch服务调试全流程 1. 引言:为什么选择OFA图像描述模型? 你有没有遇到过这样的场景?手头有一堆图片,需要为它们配上文字说明,一张张手动写描述,…...
QModMaster:5分钟掌握免费开源ModBus调试工具终极指南
QModMaster:5分钟掌握免费开源ModBus调试工具终极指南 【免费下载链接】qModbusMaster 项目地址: https://gitcode.com/gh_mirrors/qm/qModbusMaster 你是否在为工业设备调试而烦恼?面对复杂的ModBus通信协议,商业软件价格昂贵&#…...
AI图像增强工具Real-ESRGAN-GUI:让模糊影像重获新生的完整指南
AI图像增强工具Real-ESRGAN-GUI:让模糊影像重获新生的完整指南 【免费下载链接】Real-ESRGAN-GUI Lovely Real-ESRGAN / Real-CUGAN GUI Wrapper 项目地址: https://gitcode.com/gh_mirrors/re/Real-ESRGAN-GUI 你是否曾遇到珍藏的老照片因年代久远变得模糊不…...
ANR-WatchDog源码深度剖析:从线程监控到错误抛出的完整实现
ANR-WatchDog源码深度剖析:从线程监控到错误抛出的完整实现 【免费下载链接】ANR-WatchDog A simple watchdog that detects Android ANR (Application Not Responding) error and throws a meaningful exception 项目地址: https://gitcode.com/gh_mirrors/an/AN…...
Pixel Epic智识终端效果展示:跨领域研报生成一致性与专业性验证
Pixel Epic智识终端效果展示:跨领域研报生成一致性与专业性验证 1. 产品概览与核心价值 Pixel Epic智识终端是一款基于AgentCPM-Report大模型构建的专业研究报告生成工具。与传统AI工具不同,它创新性地采用了像素RPG游戏的美学设计,将枯燥的…...
Go Context 取消信号传播机制剖析
Go Context 取消信号传播机制剖析 在并发编程中,如何优雅地控制协程的生命周期是一个关键问题。Go语言通过Context机制提供了一种统一的取消信号传播方式,使得跨协程、跨层级的任务取消变得简单高效。本文将深入剖析Context的取消信号传播机制ÿ…...
基于RK3506与LVGUI的CyberGear电机交互式控制台开发实践
1. 从零搭建CyberGear电机控制环境 第一次拿到RK3506开发板和小米CyberGear电机时,我花了整整两天时间才把基础环境搭好。这里分享几个关键步骤,帮你避开我踩过的坑。 硬件连接部分要注意XT30PB插头的防呆设计,插反了会烧毁接口。建议先用万用…...
Linux下CMake多版本共存实战:不卸载旧版也能用上新功能
Linux下CMake多版本共存实战:不卸载旧版也能用上新功能 在软件开发的世界里,版本管理就像一场永不停歇的舞蹈。想象一下这样的场景:你正在维护一个历史悠久的C项目,突然客户要求你同时开发一个全新的模块,而这个模块需…...
细致配置Doctrine,专注于指定前缀表的迁移
在使用Symfony和Doctrine进行项目开发时,如何优雅地处理数据库迁移是一个常见的问题。本文将详细探讨如何配置Doctrine,使其在生成迁移文件时仅关注特定前缀的表(如pp_前缀的表),从而避免迁移文件中包含不必要的表。 背景介绍 假设你有一个Symfony项目,该项目中数据库已…...
