【多线程初阶】wait() notify()
文章目录
- 协调多个线程间的执行顺序
- join 和 wait 区别
- sleep 和 wait 区别
- wait()方法
- 线程饿死
- 调用 wait()
- 唤醒 wait()
- notify()方法
- wait() 和 notify() 需对同一对象使用
- 确保先 wait ,后 notify
- 多个线程在同一对象上wait notify随机唤醒一个wait
- notifyAll()方法
- 应用 wait() 和 notify()协调多个线程的执行示例
协调多个线程间的执行顺序
由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知.但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序
比如:球场上的每个运动员都是独立的"执行流",可以认为是一个"线程"
而完成一个具体的进攻得分动作,则需要多个运动员的相互配合,按照一定的顺序执行一定的动作,线程1 先"传球",线程2 才能"扣篮"
注意:wait,notify,notifyAll都是Object类的方法,也就是说任意对象的线程都可以进行协调执行顺序的操作
join 和 wait 区别
-
join也是等
join是等另一个线程执行完,才继续走 -
wait也是等
wait是等到另一个线程执行notify,才继续走(不需要另一个线程执行完)
wait 和 join 类似的是:都提供了"死等"(不带参数)的版本 和 “超过时间”(带参数)的版本
sleep 和 wait 区别
- 使用要求:
- wait() 必须搭配锁,先加锁,才能使用 wait,否则会抛出IllegalMonitorSteteException
- 若在synchronized内部调用后,当前线程会释放锁.并进入等待状态
- sleep() 方法可以在任意上下文使用,不需要锁
- 若在synchronized内部调用后,不会释放锁,线程进入休眠状态
- 方法所属类不同
- wait() : 属于Object类的非静态方法
- sleep() : 属于Thread类的静态方法
- 唤醒方式
- wait()需要被其他线程通过notify()或notifyAll()唤醒 或是wait(long timeout)超时唤醒
- sleep()在超过时间后自动唤醒 或是 interrupt()提前唤醒,抛出InterruptedException异常
- 用途不同
- wait()通常配合 notify() 和 notifyAll()实现线程间的协调工作
- sleep() 用于让线程暂停一段时间,比如停下来看一下日志或者我们前面保证会出现死锁的状态也是通过sleep实现的,用来模拟延时等等
- 误用sleep,sleep()并不会释放锁,可能会导致其他线程无法进入同步块,造成线程饥饿或死锁
synchronized(locker){
Thread.sleep(1000);
},这样sleep抱着锁睡~~,其他线程也无法获取这个锁
sleep()若被提前唤醒或抛出InterruptedException异常,若不处理,会导致线程提前退出
wait()方法
wait做的事情:
- 使当前执行代码的线程进行等待(把线程放到等待队列中)
- 释放当前的锁
- 满足一定条件时被唤醒,重新尝试获取这个锁
wait要搭配 synchronized来使用,脱离synchronized使用.抛出IllegalMonitorSteteException异常
wait结束等待的条件:
- 其他线程调用该对象的notify方法
- wait等待超过时间
- 其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException异常
线程饿死
当多个线程竞争一把锁的时候,获取到锁的线程如果释放了,其他是哪个线程拿到锁?—>不确定(随机调度)
操作系统的调度是随机的,其他线程都属于在锁上阻塞等待,是阻塞状态,而当前释放锁的这个线程,是就绪状态,所以很大概率还是这个线程再次拿到锁
这样就是导致其他线程一直捞不到CPU资源去执行,线程就饿死了
调用 wait()
- wait() 和 notify() 都是Object方法–>Java中的任意对象都提供了 wait() 和 notify()
- 当拿到锁的线程,发现要执行的任务,时机还不成熟,就是用 wait 进行阻塞等待(把线程放到等待队列中)
- wait() 一被调用,就会释放当前的锁
- wait的使用要搭配synchronized使用;脱离synchronized,就会抛出上述异常
- 上述抛出异常的本质:就是对未加锁的对象进行解锁操作
唤醒 wait()
- 使用其他线程该对象的notify() 唤醒wait线程,wait就会解除,重新获取到锁,继续执行并返回
- 要求synchronized的锁对象和wait的对象是同一个
notify()方法
notify 方法是唤醒等待的线程
- notify 方法也要在同步方法或同步代码块中(就是在synchronized修饰的方法或代码块中)调用,该方法是用来通知那些等待该对象的对象锁的其他线程,对其发出通知,使他们重新获取该对象的对象锁
- 如果多个线程等待,则由线程调度器随机挑选一个呈 wait 状态的线程(没有"先来后到")
- 在notify()后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁
wait() 和 notify() 需对同一对象使用
- 通过相同的对象调用 wait() 和 notify() ,是两个线程沟通的桥梁
- wait() 和 notify() 针对同一对象才会生效,不同对象使用是没有相互的影响和作用的
- wait() 操作必须搭配锁来进行,notify操作原则上不涉及加锁解锁操作,但是在Java中强制要求notify搭配synchronized使用
在Java中强制要求notify搭配synchronized使用,操作系统原生API中wait必须搭配锁使用,notify则不需要
确保先 wait ,后 notify
- 务必确保,先wait,后notify,才有作用
- 如果先notify,后wait,此时wait无法被唤醒,notify的这个线程虽然没有副作用(notify一个没有wait的对象,并不会报错),但是就相当于后wait的这个线程无法被唤醒了,他的notify在他没有wait之前就已经通知过他了,毫无作用
多个线程在同一对象上wait notify随机唤醒一个wait
- 如果有多个线程在同一个对象上wait,进行notify的时候是随机唤醒其中一个线程
- 一次notify 唤醒一个wait
- 多个线程在同一对象上等待,线程调度器随机挑选出一个呈wait的线程(并没有"先来后到")
public class Demo25 {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() ->{try{System.out.println("t1 wait 之前");synchronized (locker){locker.wait();}System.out.println("t1 wait 之后");}catch (InterruptedException e){throw new RuntimeException();}});Thread t2 = new Thread(() ->{try{System.out.println("t2 wait 之前");synchronized (locker){locker.wait();}System.out.println("t2 wait 之后");}catch (InterruptedException e){throw new RuntimeException();}});Thread t3 = new Thread(() ->{Scanner scanner = new Scanner(System.in);System.out.println("输入任意内容,唤醒一个线程");scanner.next();synchronized (locker){locker.notify();}});t1.start();t2.start();t3.start();}
}
再加一个notify,一次notify 唤醒一个 wait
public class Demo25 {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() ->{try{System.out.println("t1 wait 之前");synchronized (locker){locker.wait();}System.out.println("t1 wait 之后");}catch (InterruptedException e){throw new RuntimeException();}});Thread t2 = new Thread(() ->{try{System.out.println("t2 wait 之前");synchronized (locker){locker.wait();}System.out.println("t2 wait 之后");}catch (InterruptedException e){throw new RuntimeException();}});Thread t3 = new Thread(() ->{Scanner scanner = new Scanner(System.in);System.out.println("输入任意内容,唤醒一个线程");scanner.next();synchronized (locker){locker.notify();}System.out.println("输入任意内容,唤醒另一个线程");scanner.next();synchronized (locker){locker.notify();}});t1.start();t2.start();t3.start();}
}
notifyAll()方法
- 对于上述多个线程在同一对象上等待,我们可以考虑使用notifyAll.唤醒同一对象的所有wait的线程
- 虽然同时唤醒了,t1 和 t2 由于 wait 唤醒之后,要重新加锁,其中某个线程,先加上锁,开始执行,另一个线程因为加锁失败,再次阻塞等待
- 等到先走的线程解锁了,后走的线程才能加上锁,继续执行
总结:
- 因为这个原因,notifyAll()在实际开发中,虽然可以唤醒所有的wait(),但使用并不多
- notifyAll()在唤醒其中一个wait状态的线程时,其他的线程依旧因为wait()尝试重新获取锁对象,而陷入阻塞等待
- 而且一般这些wait的线程都是干同样的工作的,唤醒谁,其实都一样~~
应用 wait() 和 notify()协调多个线程的执行示例
题目:
有三个线程,分别只能打印A ,B和C
要求按顺序打印ABC,打印10次
输出示例:
ABC
ABC
ABC…
public class Demo26 {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();}});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();}});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();}});t1.start();t2.start();t3.start();// 主线程中, 先通知一次 locker1, 让上述逻辑从 t1 开始执行// 需要确保上述三个线程都执行到 wait, 再进行 notifyThread.sleep(1000);//wait 确保三个线程都wait了,再执行notifysynchronized (locker1) {locker1.notify();}}
}
相关文章:

【多线程初阶】wait() notify()
文章目录 协调多个线程间的执行顺序join 和 wait 区别sleep 和 wait 区别 wait()方法线程饿死调用 wait()唤醒 wait() notify()方法wait() 和 notify() 需对同一对象使用确保先 wait ,后 notify多个线程在同一对象上wait notify随机唤醒一个wait notifyAll()方法应用 wait() 和…...

安全-JAVA开发-第二天
Web资源访问的流程 由此可见 客户访问JAVA开发的应用时 会先通过 监听器(Listener)和 过滤器(Filter) 今天简单的了解下这两个模块的开发过程 监听器(Listener) 主要是监听 我们触发了什么行为 并进行反应…...

Python基础:文件简单操作
🍃引言 手把手带你快速上手Python Python基础专栏 一、🍃文件是什么 变量是把数据保存到内存中. 如果程序重启/主机重启, 内存中的数据就会丢失。 要想能让数据被持久化存储, 就可以把数据存储到硬盘中. 也就是在文件中保存。 通过文件的后缀名, 可以看…...

深度学习项目之RT-DETR训练自己数据集
RT-DETR 1.模型介绍📌 什么是 RT-DETR ?📖 核心改进点📊 结构示意🎯 RT-DETR 优势⚠️ RT-DETR 缺点📈 应用场景📑 论文 & 官方仓库2.模型框架3.Yaml配置文件4.训练脚本5.训练完成截图6.总结…...

以太网帧结构和封装【二】-- IP头部信息
1字节 byte 8比特 bit 【位和比特是同一个概念】 比特/位,字节之间的关系是: 位(Bit) 中文名:位(二进制位)。 英文名:Bit(Binary Digit 的缩写)。 含义&…...
mysql 悲观锁和乐观锁(—悲观锁)
适合悲观锁的使用场景: 悲观锁更适合在,写操作较多、并发冲突高、业务需要强一致性等场景下使用悲观锁。 如何使用悲观锁: 悲观锁主要通过以下两个 SQL语句实现: 1、SELECT...FOR UPDATE; 这个语句会在所查询中的数据行上设置排…...

Promtail采集服务器本地日志存储到Loki
✅ 一、前提条件 已安装 Loki 服务 日志文件目录可访问(如 /var/log) 具备 sudo 权限 🧩 二、下载 Promtail 二进制文件 # 替换为你想要的版本 VERSION"3.5.1"# 创建目录 sudo mkdir -p /opt/promtail cd /opt/promtail# 下载并…...
python第31天打卡
import numpy as np from tensorflow import keras from tensorflow.keras import layers, optimizers, utils, datasets# 数据加载和预处理函数 def load_and_preprocess_data():(x_train, y_train), (x_test, y_test) datasets.mnist.load_data()# 重塑并归一化图像数据x_tr…...
4.1 HarmonyOS NEXT原生AI能力集成:盘古大模型端侧部署与多模态交互实战
HarmonyOS NEXT原生AI能力集成:盘古大模型端侧部署与多模态交互实战 在HarmonyOS NEXT的全场景生态中,原生AI能力成为连接设备、服务与用户的核心纽带。通过盘古大模型端侧轻量化部署、多模态交互技术及环境感知系统,开发者能够构建"主…...

学习STC51单片机27(芯片为STC89C52RCRC)
每日一言 你读过的书、走过的路、流过的汗,终将成就独一无二的你。 硬件:LCD1602液晶显示 非标协议外设 概述 LCD1602(Liquid Crystal Display)是一种工业字符型液晶,能够同时显示 1602 即 32 字符(16列两行) 那我…...
PAT-甲级JAVA题解(更新中...)
使用JAVA语言进行算法练习,但是有些会出现运行超时情况. 题目链接A1001A1001-PAT甲级JAVA题解 AB FormatA1005A1005-PAT甲级JAVA题解 Spell It RightA1006A1006-PAT甲级JAVA题解 Sign In and Sign OutA1011A1011-PAT甲级JAVA题解World Cup BettingA1012A1012 PAT甲级JAVA题解 …...
Deep Chat:重塑人机对话边界的开源智能对话框架—— 让下一代AI交互无缝融入你的应用
在AI助手泛滥的今天,开发体验碎片化、功能扩展性差、多模态支持不足成为行业痛点。由开发者Ovidijus Parsiunas发起的开源项目 Deep Chat(https://github.com/OvidijusParsiunas/deep-chat),正以模块化设计 全栈兼容性颠覆传统聊…...

DA14531_beacon_大小信标设备开发
蓝牙信标是一款通过广播指定蓝牙信号,实现信标信号扫描、识别和获得辅助信息的电子产品。 不同品名的蓝牙信标采用相同的 UUID 和广播信号格式,但在 MAC 地址、工作寿命、体积和广播周期上有所差异。 小武编程巧用DA14531开发一款蓝牙信标....

【算法训练营Day06】哈希表part2
文章目录 四数相加赎金信三数之和四数之和 四数相加 题目链接:454. 四数相加 II 这个题注意它只需要给出次数,而不是元组。所以我们可以分治。将前两个数组的加和情况使用map存储起来,再将后两个数组的加和情况使用map存储起来,ke…...

Word双栏英文论文排版攻略
word写双栏英文论文的注意事项 排版首先改字体添加连字符还没完呢有时候设置了两端对齐会出现这样的情况: 公式文献 等我下学期有时间了,一定要学习Latex啊,word写英文论文,不论是排版还是公式都很麻烦的,而Latex一键就…...

乡村三维建模 | 江苏农田无人机建模案例
测绘是农田建设的基础工作,测绘的质量和效率直接影响农田建设的进度和成果。传统的人工测量、地面测量等测绘手段,存在效率低、精度差、受环境影响大、成本高等缺点,难以满足高标准农田建设的要求。而无人机倾斜摄影技术具有高效、精确、灵活…...

2025 5 月 学习笔记
计算高斯半径,用于生成高斯热图 这个的意义是什么 有什么作用? 14 核心意义:平衡定位精度与检测鲁棒性 在基于热图的目标检测方法(如CenterNet、CornerNet等)中,计算高斯半径的核心意义在于在精确…...

SpringBoot(七) --- Redis基础
目录 前言 一、Redis入门 二、Redis常用数据类型 三、Redis常用命令 1. 字符串操作命令 2. 哈希操作命令 3. 列表操作命令 4. 集合操作命令 5. 有序集合操作命令 6.通用命令 四、在Java中操作Redis 前言 Redis是一个基于内存的key-value结构数据库,有以下…...
Oracle 故障实例 - 通过备份恢复到某时间点 故障恢复
一、环境和故障描述 1.数据库版本:oracle 11g , linux ;OA系统的后台数据库。 2. 同事在做正式机数据迁移到测试机时,不小心删除了正式机的数据。 导致大量生产数据丢失,系统故障。 3.万幸的是正式机每日都做了数据备份&#x…...
滑动智能降级:Glide优化加载性能的黑科技
简介 在移动应用开发中,图片加载性能直接关系到用户体验,尤其在列表快速滑动场景下,如何平衡流畅度与流量消耗成为开发者面临的核心挑战。本文将深入探讨Glide库的智能降级技术,通过滑动速度动态调整图片加载策略,实现流量节省35%、首屏加载速度提升40%的显著效果。我们将…...
【前端并发请求控制:必要性与实现策略】
前端并发请求控制:必要性与实现策略 一、引言 在现代 Web 开发中,处理大量异步请求是一个常见场景。虽然浏览器和服务器都有其并发限制机制,但前端实现并发控制仍然具有其独特的价值和必要性。本文将深入探讨这个话题。 二、现有的并发限制…...
LeetCode 139. 单词拆分(Word Break) - 动态规划深度解析
文章目录 问题描述动态规划解法解法核心思路完整代码实现关键代码解析1. 数据结构初始化2. 动态规划数组3. 核心循环逻辑4. 子串区间理解(关键)示例演算复杂度分析算法优化点总结本文详细解析LeetCode 139题"单词拆分"的动态规划解法,涵盖核心思路、代码实现、区间…...
@Prometheus动态配置管理-ConsulConfd
文章目录 动态配置管理 Consul Confd**一、目标****二、架构组件****三、环境准备****四、配置 Consul**1. 注册监控目标(服务发现)2. 存储告警规则(KV 存储) **五、配置 Confd**1. 监控目标模板配置2. 告警规则模板配置 **六、配…...
CentOS7 + JDK8 虚拟机安装与 Hadoop + Spark 集群搭建实践
前言 在大数据时代,Hadoop 和 Spark 是两种非常重要的分布式计算框架。本文将详细介绍如何在 CentOS7 JDK8 的虚拟机环境中搭建 Hadoop Spark 分布式集群,包括 Spark Standalone 和 Hadoop Spark on YARN 两种模式,并提供具体的代码示例。…...

从OSI到TCP/IP:网络协议的演变与作用
个人主页:chian-ocean 文章专栏-NET 从OSI到TCP/IP:网络协议的演变与作用 个人主页:chian-ocean文章专栏-NET 前言网络发展LANWAN 协议举个例子: 协议的产生背景 协议的标准化OSI模型参考OSI各个分层的作用各层次的功能简介 TCP/…...

Stream流性能分析及优雅使用
文章目录 摘要一、Stream原理解析1.1、Stream总概1.2、Stream运行机制1.2.1、创建结点1.2.1、搭建流水线1.2.3、启动流水线 1.3、ParallelStream 二、性能对比三、优雅使用3.1 Collectors.toMap()3.2 findFirst(),findAny()3.3 增删元素3.4 ParallelStream 四、总结…...
iOS 电子书听书功能的实现
在 iOS 应用中实现电子书听书(文本转语音)功能,可以通过系统提供的 AVFoundation 框架实现。以下是详细实现步骤和代码示例: 核心步骤: 导入框架创建语音合成器配置语音参数实现播放控制处理后台播放添加进度跟踪 完整…...

【和春笋一起学C++】(十七)C++函数新特性——内联函数和引用变量
C提供了新的函数特性,使之有别于C语言。主要包括: 内联函数;按引用传递变量;默认参数值;函数重载(多态);模版函数; 因篇幅限制,本文首先介绍内联函数和引用…...
GitHub 趋势日报 (2025年06月02日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 1339 prompt-eng-interactive-tutorial 1080 courses 624 onlook 596 system-desi…...
卫星的“太空陀螺”:反作用轮如何精准控制姿态?
卫星的“太空陀螺”:反作用轮如何精准控制姿态? 在距地面500公里的轨道上,一颗遥感卫星正以7.8km/s的速度飞越目标区域。此时星载计算机发出指令:“滚转15并对准目标点”。短短数秒后,数吨重的卫星如同被无形之手推动般…...