JavaWeb12-线程通讯(线程等待和唤醒)
目录
1.方法介绍
1.1.wait()/wait(long timeout):让当前线程进入等待状态。
1.1.1.wait执行流程
1.1.2.wait结束等待的条件
1.1.3.wait() VS wait(long timeout)
1.1.4.为什么wait要放在Object中?
--->PS:wait(0) 和 sleep(0) 的区别
--->PS:wait 和 sleep 释放锁行为的区别
--->PS:(常见面试题)wait 和 sleep 的区别(小结)
--->PS:3种方法让线程进入休眠/等待状态
1.2.notify():唤醒当前对象上一个休眠的线程(随机)。
1.3.notifyAll():唤醒当前对象上的所有线程。
2.注意事项:
由于线程之间是抢占式执⾏的,因此线程之间执⾏的先后顺序难以预知。但是实际开发中有时候希望合理地协调多个线程之间的执⾏先后顺序。
球场上的每个运动员都是独⽴的 "执⾏流",可以认为是⼀个 "线程"。⽽完成⼀个具体的进攻得分动作,则需要多个运动员相互配合, 按照⼀定的顺序执⾏⼀定的动作,线程1 先 "传球",线程2 才能 "扣篮"。
1.方法介绍
完成这个协调⼯作(线程通讯),主要涉及到以下三个⽅法。注意这三个方法都是对象级别的(需要通过"对象."来调用),【不是锁级别,如Thread.sleep()】都是Object类的内置方法。
1.1.wait()/wait(long timeout):让当前线程进入等待状态。
1.1.1.wait执行流程
- 使当前执⾏代码的线程进⾏等待。(把线程放到等待队列中)。
- 释放当前的锁。
- 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁。
1.1.2.wait结束等待的条件
- 其他线程调⽤该对象的 notify ⽅法。wait/notify 唤醒顺序是无序的。
- wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间)。
- 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常。
/*** wait使用*/
public class WaitDemo {public static void main(String[] args) {Object lock = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1执行完成");},"线程1");t1.start();}
} 
import java.util.concurrent.TimeUnit;public class WaitSleepDemo7 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:结束执行");}System.out.println("线程1:终止执行");},"wait0");t1.start();TimeUnit.SECONDS.sleep(1);System.out.println("执行线程1的终止方法");t1.interrupt();}
}
1.1.3.wait() VS wait(long timeout)
1-不同点:wait(long timeout):当线程超过了设置的时间之后,自动恢复执行;而wait():无限等待状态。(0表示无限等待状态)

2-不同点:wait(long timeout):线程会进入WAITING状态;而wait():线程会进入TIMED_WAITING状态。
import java.time.LocalDateTime;public class WaitDemo4 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();new Thread(() -> {System.out.println("线程1:开始执行");synchronized (lock){try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行完成");}},"无参wait线程").start();new Thread(() -> {synchronized (lock2){System.out.println("线程2:开始执行 |" + LocalDateTime.now());try {lock2.wait(60 * 60 * 60 * 1000); //1h} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行完成 |" + LocalDateTime.now());}},"有参wait线程").start();}
}



3-共同点:无论是wait(long timeout)还是wait(),都会使当前线程进入休眠状态。
4-共同点:无论是wait(long timeout)还是wait(),都能使用notify/notifyAll进行唤醒。
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;public class WaitDemo6 {public static void main(String[] args) {Object lock = new Object();new Thread(() -> {System.out.println("线程1:开始执行");synchronized (lock){try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行完成");}},"无参wait线程").start();new Thread(() -> {synchronized (lock){System.out.println("线程2:开始执行 |" + LocalDateTime.now());try {lock.wait(60 * 60 * 60 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行完成 |" + LocalDateTime.now());}},"有参wait线程").start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock){System.out.println("唤醒所有线程");lock.notifyAll();}}).start();}
}

1.1.4.为什么wait要放在Object中?
wait 使用要加锁,也就是要操作锁,锁是针对对象级别的而非线程级别的,线程和对象是⼀对多,所以 wait 最便利的方式是放在 Object 中。
wait(num)和sleep(num):当num>0时,二者执行效果一样。
--->PS:wait(0) 和 sleep(0) 的区别
- wait(0) 表示无期限地等待,直到有线程唤醒它为止。
- Thread.sleep(0) 调用后会让出CPU执行权,让线程稍做休眠,然后重新调度,重新触发一次 CPU 竞争,不管是否竞争到CPU执行权,都会继续执行,直到执行结束。(类似于Thread.yield())
/*** wait(0)和sleep(0)*/ public class WaitSleepDemo7 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行结束");}},"wait(0)");t1.start();Thread t2 = new Thread(() -> {System.out.println("线程2:开始执行");try {Thread.sleep(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行结束");},"sleep(0)");t2.start();} }
--->PS:wait 和 sleep 释放锁行为的区别
wait和sleep在有锁的情况下,锁的处理行为是完全不同的:
- wait方法(不管是有参还是无参)在执行时都会释放锁。
- sleep方法不会释放锁。
import java.util.concurrent.TimeUnit;/*** wait和sleep释放锁行为的区别*/ public class WaitSleepDemo8 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(3 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:结束执行");}},"wait");t1.start();Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("线程2:开始执行");try {Thread.sleep(3 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:结束执行");}},"sleep");t2.start();//创建2个线程,先让线程休眠1秒之后,尝试获取锁,看能不能获取到锁//如果可以获取到锁,说明休眠时线程是释放锁的,而如果获取不到锁,说明是不释放锁的Thread t3 = new Thread(() -> {try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("尝试获取wait方法的锁");synchronized (lock){System.out.println("成功获取wait的锁");}},"wait2");t3.start();Thread t4 = new Thread(() -> {try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("尝试获取sleep方法的锁");synchronized (lock2){System.out.println("成功获取sleep的锁");}},"sleep2");t4.start();} }
为什么wait释放锁而sleep不释放锁?
JVM 强制语法检查,wait 方法默认等待无期限。
--->PS:(常见面试题)wait 和 sleep 的区别(小结)
二者的相同点:
- 都可以让线程休眠。
- 都可以响应 interrupt 中断的请求。
- wait(num) 和 sleep(num):当num>0时,二者执行效果一样。
二者的不同点:
- wait 必须在 synchronized 中使用;而 sleep 却不用。
- wait 是 Object 的方法(属于对象级别);而sleep 是 Thread 的方法(属于线程级别)。
- wait 释放锁;而sleep 不释放锁。
- wait 有可能无限期地等待下去;而sleep 有明确的终止等待时间。 即:wait 可传参,也可不传参;而 sleep必须要传递一个数值类型的参数,否则会报错。
- wait 和 sleep 产生的线程状态是不同的:无参的 wait 是 WAITING 状态;而sleep 是 TIMED_WAITING 状态。
- 一般情况下(响应 interrupt 除过),wait 可以接收一个 notify/notifyAll 之后就继续执行;而 sleep 只能等待超过时间之后再恢复执行。
- wait(0) 表示无期限地等待;而sleep(0)表示重新触发一次 CPU 竞争。
--->PS:3种方法让线程进入休眠/等待状态
- sleep(传参设置休眠时间;不可唤醒)
- TimeUnit(传参设置休眠时间;不可唤醒)
- wait(可传参设置休眠时间,也可不传参无限等待;可以唤醒)
1.2.notify():唤醒当前对象上一个休眠的线程(随机)。
-
⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
-
如果有多个线程等待,则由线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到") (官方是随机的,但具体实现针对不同的JVM是不一样的)

/*** notify使用*/
public class WaitDemo2 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期的等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1执行完成");},"线程1");Thread t2 = new Thread(() -> {System.out.println("线程2开始执行");try {synchronized (lock) {System.out.println("线程2调用wait方法....");//无限期的等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2执行完成");},"线程2");Thread t3 = new Thread(() -> {System.out.println("线程3开始执行");try {synchronized (lock2) {System.out.println("线程3调用wait方法....");//无限期的等待状态lock2.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程3执行完成");},"线程3");t1.start();t2.start();t3.start();//唤醒lock对象上休眠的线程(随机唤醒一个)Thread t4 = new Thread(() -> {try {Thread.sleep(1500);} catch (InterruptedException e) {}System.out.println("线程4:开始执行,唤醒线程");synchronized (lock) {//发出唤醒通知lock.notify();System.out.println("线程4:执行了唤醒操作");try{Thread.sleep(2000);} catch (InterruptedException e) {}System.out.println("线程4:synchronized执行完了");}}, "线程4");t4.start();}
}

1.3.notifyAll():唤醒当前对象上的所有线程。

/*** notifyAll使用*/
public class WaitDemo3 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期的等待状态lock.wait();System.out.println("线程1:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程1执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程1");Thread t2 = new Thread(() -> {System.out.println("线程2开始执行");try {synchronized (lock2) {System.out.println("线程2调用wait方法....");//无限期的等待状态lock2.wait();System.out.println("线程2:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程2执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程2");Thread t3 = new Thread(() -> {System.out.println("线程3开始执行");try {synchronized (lock) {System.out.println("线程3调用wait方法....");//无限期的等待状态lock.wait();System.out.println("线程3:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程3执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程3");t1.start();t2.start();t3.start();//唤醒lock对象上休眠的线程(随机唤醒一个)Thread t4 = new Thread(() -> {try {Thread.sleep(500);} catch (InterruptedException e) {}System.out.println("线程4:开始执行,唤醒线程");synchronized (lock){//发出唤醒通知lock.notifyAll();System.out.println("线程4:执行了唤醒操作");try{Thread.sleep(2000);} catch (InterruptedException e) {}System.out.println("线程4:synchronized执行完了");}}, "线程4");t4.start();}
}

2.注意事项:
①wait/notify/notifyAll必须要配合synchronized一起使用,否则会报错(JVM的强制规定,之所以这样规定,是为了解决线程通讯时执行混乱的问题,synchronized起到约束限制的作用)。

②wait/notify/notifyAll进行synchronized加锁,一定要使用同一个对象进行加锁。
③当调用了notify/notifyAll之后,当前线程不会⻢上释放该对象锁,要等到执⾏notify/notifyAll⽅法的线程将程序执⾏完,也就是退出同步代码块(synchronized)之后才会释放对象锁。【程序也并不会立即恢复执行,而是尝试获取锁,只有得到锁之后才能继续执行。】
④notify/notifyAll可以多次调用,也可以在wait之前调用(是无用功)。
⑤notifyAll 并不是唤醒所有 wait 等待的线程,而是唤醒当前对象处于 wait 等待的所有线程。
相关文章:
JavaWeb12-线程通讯(线程等待和唤醒)
目录 1.方法介绍 1.1.wait()/wait(long timeout):让当前线程进入等待状态。 1.1.1.wait执行流程 1.1.2.wait结束等待的条件 1.1.3.wait() VS wait(long timeout) 1.1.4.为什么wait要放在Object中? --->PS:wait(0) 和 sleep(0) 的区…...
江苏专转本如何事半功倍的备考
专转本如何事半功倍的备考 一个人学习成绩的优劣取决于他的学习能力,学习能力包括三个要素:规范的学习行为;良好的学习习惯;有效的学习方法。有了规范的学习行为才能培养出良好的学习习惯,形成了良好的学习习惯就会形成…...
linux下安装mongoDB
一、下载mongoDB包 下载地址: https://www.mongodb.com/try/download/community 个人建议:如果是学习阶段,使用5以下版本更好些。 二、安装及配置 1、安装 # 1、解压 $ tar -zxvf mongodb-linux-x86_64-rhel70-4.4.19-rc1.tgz# 2、迁移目…...
掌握MySQL分库分表(七)广播表、绑定表实战,水平分库+分表实现及之后的查询和删除操作
文章目录什么是广播表广播表实战数据库配置表Java配置实体类配置文件测试广播表水平分库分表配置文件运行测试什么是绑定表?绑定表实战配置数据库配置Java实体类配置文件运行测试水平分库分表后的查询和删除操作查询操作什么是广播表 指所有的分片数据源中都存在的…...
企业为什么需要数据可视化报表
数据可视化报表是在商业环境、市场环境已经改变之后,发展出来为当前企业提供替代解决办法的重要方案。而且信息化、数字化时代,很多企业已经进行了初步的信息化建设,沉淀了大量业务数据,这些数据作为企业的资产,是需要…...
5个有效的华为(HUAWEI)手机数据恢复方法
5个有效的手机数据恢复方法 华为智能手机中的数据丢失比许多人认为的更为普遍。发生这种类型的丢失有多种不同的原因,因此数据恢复软件的重要性。您永远不知道您的智能手机何时会在这方面垮台;因此,预防总比哀叹好,这就是为什么众…...
【Java并发编程】线程安全(一)Synchronized原理
Synchronized底层实现 简单来说,Synchronized关键字的执行主体是线程对象,加锁是通过一个锁对象来完成的是,而锁对象底层关联了一个c源码的monitor的对象,monitor对象底层又对应了操作系统级别的互斥锁,同一时刻只有一…...
[apollo]vue3.x中apollo的使用
[apollo]vue3.x中apollo的使用通过客户端获取Apollo配置环境工具的安装获取Apollo配置相关代码错误提示Uncaught (in promise) Error: Apollo client with id default not found. Use provideApolloClient() if you are outside of a component setup通过开放接口获取Apollo配置…...
system()函数启用新进程占有原进程的文件描述符表的问题
我在A程序中占用了/dev/video0这个独占模式的设备文件,在A中用system函数启用了B程序,B程序的代码中并不包含对/dev/video0的访问,但是我发现B程序也占用了/dev/video0,并且我在A程序中关闭了/dev/video0后,A程序不再占…...
nignx(安装,正反代理,安装tomcat设置反向代理,ip透传)
1安装nginx 安装wget Yum install -y wget 下载(链接从官网找到右键获取) 以下过程root 安装gcc Yum -y install gcc c 安装pcre Yum install -y pcre pcre-devel Openssl Yum install -y openssl openssl-devel 安装zlib Yum install -y zlib zlib-devel 安装make Yum inst…...
sklearn模块常用内容解析笔记
文章目录 回归模型评价指标R2_score预备知识R2_score计算公式r2_score使用方法注意事项参考文献回归模型评价指标R2_score 回归模型的性能的评价指标主要有:RMSE(平方根误差)、MAE(平均绝对误差)、MSE(平均平方误差)、R2_score。但是当量纲不同时,RMSE、MAE、MSE难以衡量模…...
我的 System Verilog 学习记录(2)
引言 从本文开始,就开始系统学习 System Verilog ,不只是语法,还有结合 Questa Sim 的实际编程练习、Debug。 本文简单介绍 System Verilog 语言的用途以及学习的必要性。 前文链接: 我的 System Verilog 学习记录(…...
【调研报告】Monorepo 和 Multirepo 的风格对比及使用示例
带有权重的Monorepo和Multirepo对比 功能/特性MonorepoMultirepoMonorepo权重值Multirepo权重值代码管理管理多个代码库更加复杂管理单个代码库更加简单37依赖管理可以简化依赖管理依赖冲突可能会更加困难73构建和部署构建和部署更加容易构建和部署可能需要更多的配置82团队协…...
Retrofit源码分析
文章目录一、简介二、源码分析2.1Retrofit的本质流程2.2源码分析2.2.1 创建Retrofit实例步骤1步骤2步骤3步骤4步骤5总结2.2.2创建网络请求接口的实例外观模式 & 代理模式1.外观模式2. 代理模式步骤3步骤4总结2.2.3执行网络请求同步请求OkHttpCall.execute()1.发送请求过程2…...
Mybatis-Plus入门系列(20) -兼容多种数据库
有道无术,术尚可求,有术无道,止于术。 文章目录前言方案分析1. 分页2. XML自定义SQL案例演示1. 配置2. 简单分页查询3. 带方言的分页查询参考前言 在我们实际开发软件产品过程中,数据库的类型可能不是确定的,也有客户…...
JetPack板块—Android X解析
Android Jetpack简述 AndroidX 是Android团队用于在Jetpack中开发,测试,打包,发布和版本管理的开源项目。相比于原来的Android Support库,AndroidX 可以称得上是一次重大的升级改进。 和Support库一样,AndroidX与Android 操作系…...
C++学习笔记-数字
当我们使用数字时,通常我们使用原始数据类型,例如 int,short,long,float 和 double 等。数字数据类型,它们的可能值和取值范围在讨论 C 数据类型时已经解释了。 C 定义数字 我们已经在之前笔记的各种实例…...
Nginx——Nginx的基础原理
摘要 Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器,是一个高性能的HTTP和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器。Nginx 是由俄罗斯人 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。…...
服务端开发Java之备战秋招面试篇1
在这个面试造火箭工作拧螺丝的时代背景下,感觉不是很好,不过还好也是拿到了还行的offer,准备去实习了,接下来就是边实习边准备秋招了,这半年把(技术栈八股文面经算法题项目)吃透,希望…...
【C++的OpenCV】第三课-OpenCV图像加载和显示
我们开始学习OpenCV一、OpenCV加载图片和显示图片1.1 imread()函数的介绍1.2 cv::namedWindow()函数的介绍1.4 imshow()函数介绍1.5 Mat容器介绍二、 代码实例(带注释)2.1 代码2.2 执行结果一、OpenCV加载图片和显示图片 本章节中,将会学习到…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...



