Java多线程与高并发专题——Condition 和 wait/notify的关系
引入
上一篇关于Condition,我们对Condition有了进一步了解,在之前生产/消费者模式一文,我们讲过如何用 Condition 和 wait/notify 来实现生产者/消费者模式,其中的精髓就在于用Condition 和 wait/notify 来实现简易版阻塞队列,我们先来分别回顾一下这两段代码。
用 Condition 实现简易版阻塞队列
代码如下所示:
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/*** 一个使用 Condition 实现的阻塞队列类。* 该类提供了一个线程安全的队列,支持在队列满时阻塞插入操作,* 在队列空时阻塞移除操作。*/
public class MyBlockingQueueForCondition {// 存储元素的队列private Queue queue;// 队列的最大容量private int max = 16;// 用于线程同步的可重入锁private ReentrantLock lock = new ReentrantLock();// 当队列不为空时的条件private Condition notEmpty = lock.newCondition();// 当队列不为满时的条件private Condition notFull = lock.newCondition();/*** 构造函数,初始化队列的最大容量。* * @param size 队列的最大容量*/public MyBlockingQueueForCondition(int size){this.max = size;queue = new LinkedList();}/*** 向队列中插入一个元素。* 如果队列已满,线程将被阻塞,直到队列有空间。* * @param o 要插入的元素* @throws InterruptedException 如果线程在等待时被中断*/public void put(Object o) throws InterruptedException {// 获取锁lock.lock();try {// 当队列已满时,线程等待while (queue.size() == max) {notFull.await();}// 向队列中添加元素queue.add(o);// 通知所有等待队列不为空的线程notEmpty.signalAll();} finally {// 释放锁lock.unlock();}}/*** 从队列中移除并返回一个元素。* 如果队列为空,线程将被阻塞,直到队列中有元素。* * @return 队列中的第一个元素* @throws InterruptedException 如果线程在等待时被中断*/public Object take() throws InterruptedException {// 获取锁lock.lock();try {// 当队列为空时,线程等待while (queue.size() == 0) {notEmpty.await();}// 从队列中移除并获取元素Object item = queue.remove();// 通知所有等待队列不为满的线程notFull.signalAll();return item;} finally {// 释放锁lock.unlock();}}
}
在上面的代码中,首先定义了一个队列变量 queue,其最大容量是 16;然后定义了一个ReentrantLock 类型的 Lock 锁,并在 Lock 锁的基础上创建了两个 Condition,一个是 notEmpty,另一个是 notFull,分别代表队列没有空和没有满的条件;最后,声明了 put 和 take 这两个核心方法。
用 wait/notify 实现简易版阻塞队列
我们再来看看如何使用 wait/notify 来实现简易版阻塞队列,代码如下:
import java.util.LinkedList;
/*** 自定义阻塞队列类,使用 wait() 和 notifyAll() 方法实现线程同步。*/
public class MyBlockingQueueForWaitNotify {/*** 队列的最大容量*/private int maxSize;/*** 存储队列元素的链表*/private LinkedList<Object> storage;/*** 构造函数,初始化队列的最大容量和存储链表。** @param size 队列的最大容量*/public MyBlockingQueueForWaitNotify (int size) {// 将传入的最大容量赋值给类的成员变量this.maxSize = size;// 初始化存储链表storage = new LinkedList<>();}/*** 向队列中添加一个元素。如果队列已满,则线程进入等待状态。** @throws InterruptedException 如果线程在等待过程中被中断*/public synchronized void put() throws InterruptedException {// 当队列已满时,当前线程进入等待状态while (storage.size() == maxSize) {this.wait();}// 向队列中添加一个新元素storage.add(new Object());// 唤醒所有等待的线程this.notifyAll();}/*** 从队列中取出一个元素。如果队列为空,则线程进入等待状态。** @throws InterruptedException 如果线程在等待过程中被中断*/public synchronized void take() throws InterruptedException {// 当队列为空时,当前线程进入等待状态while (storage.size() == 0) {this.wait();}// 从队列中移除并打印第一个元素System.out.println(storage.remove());// 唤醒所有等待的线程this.notifyAll();}
}
如代码所示,最主要的部分仍是 put 与 take 方法。我们先来看 put 方法,该方法被 synchronized 保护,while 检查 List 是否已满,如果不满就往里面放入数据,并通过 notifyAll() 唤醒其他线程。同样,take 方法也被 synchronized 修饰,while 检查 List 是否为空,如果不为空则获取数据并唤醒其他线程。
在生产/消费者模式中,有对这两段代码的详细讲解,遗忘的小伙伴可以到前面复习一下。
Condition 和 wait/notify的关系
对比上面两种实现方式的 put 方法,会发现非常类似,此时让我们把这两段代码同时列在屏幕中,然后进行对比:
public void put(Object o) throws InterruptedException {lock.lock();try {while (queue.size() == max) {notFull.await();}queue.add(o);notEmpty.signalAll();} finally {lock.unlock();}
} | public synchronized void put() throws InterruptedException {while (storage.size() == maxSize) {this.wait();}storage.add(new Object()); this.notifyAll(); } |
可以看出,左侧是 Condition 的实现,右侧是 wait/notify 的实现:
- lock.lock() 对应进入 synchronized 方法
- condition.await() 对应 object.wait()
- condition.signalAll() 对应 object.notifyAll()
- lock.unlock() 对应退出 synchronized 方法
实际上,如果说 Lock 是用来代替 synchronized 的,那么 Condition 就是用来代替相对应的 Object 的wait/notify/notifyAll,所以在用法和性质上几乎都一样。
Condition 把 Object 的 wait/notify/notifyAll 转化为了一种相应的对象,其实现的效果基本一样,但是把更复杂的用法,变成了更直观可控的对象方法,是一种升级。
await 方法会自动释放持有的 Lock 锁,和 Object 的 wait 一样,不需要自己手动释放锁。
另外,调用 await 的时候必须持有锁,否则会抛出异常,这一点和 Object 的 wait 一样。
总结
Condition 是对 wait/notify 的改进和扩展,提供了更高的灵活性和可读性。如果需要更复杂的线程通信机制,建议使用 Condition;如果场景简单,可以继续使用 wait/notify。
下面我们梳理总结一下,核心异同,以及各自适用场景:
相似点
目的相同:两者都是用于实现线程间的通信和同步。它们允许一个线程等待某个条件满足,然后由另一个线程通知它条件已经满足,从而继续执行。
等待和通知机制:都涉及线程进入等待状态,然后被其他线程通知唤醒。在等待期间,线程会释放锁,以便其他线程可以进入同步块修改共享状态。
线程通信:两者都用于线程间的通信,允许线程等待或唤醒其他线程。
需要锁:两者都需要与锁配合使用,wait/notify 依赖 synchronized,而 Condition 依赖 Lock。
不同点
| 特性 | wait/notify | Condition |
|---|---|---|
| 锁的管理 | 隐式锁(通过 synchronized) | 显式锁(通过 Lock) |
| 灵活性 | 较低,只能有一个等待队列 | 较高,可以有多个条件变量(多个等待队列) |
| 可读性 | 较低,代码容易变得复杂 | 较高,代码更清晰 |
| 中断处理 | 不支持中断 | 支持中断(awaitUninterruptibly() 等) |
| 等待条件 | 无法指定多个条件 | 可以指定多个条件(newCondition()) |
适用场景
wait/notify:适用于简单的线程通信场景。
在 Java 中,wait、notify和notifyAll是Object类的方法。当一个线程调用一个对象的wait方法时,它会进入等待状态,直到另一个线程调用同一个对象的notify或notifyAll方法。通常在使用synchronized关键字实现同步的时候使用。例如,一个线程在同步块中调用wait方法等待某个条件,另一个线程在同步块中改变了这个条件后调用notify或notifyAll方法通知等待的线程。
Condition:适用于复杂的线程通信场景,尤其是需要多个条件变量的场景。
在 Java 中,Condition是在java.util.concurrent.locks包下的一个接口,它是对传统的对象监视器方法(如wait、notify和notifyAll)的一种替代,用于更灵活地实现线程间的通信和等待。通常在使用ReentrantLock实现同步的时候,配合Condition来实现线程间的等待和通知。比如,当一个线程需要等待某个条件满足时,它可以调用Condition的await方法进入等待状态,直到另一个线程调用signal或signalAll方法来通知它条件已经满足。
相关文章:
Java多线程与高并发专题——Condition 和 wait/notify的关系
引入 上一篇关于Condition,我们对Condition有了进一步了解,在之前生产/消费者模式一文,我们讲过如何用 Condition 和 wait/notify 来实现生产者/消费者模式,其中的精髓就在于用Condition 和 wait/notify 来实现简易版阻塞队列&am…...
mysql-分区和性能
mysql自身只支持表的横向分区。 常听到开发人员说“”对表做个分区“,然后数据的查询就会快了。这是真的吗?实际上可能跟根本感觉不到查询速度的提升,甚至会发现查询速度急剧下降。因此,在合理使用分区之前,必须了解分…...
使用matlab进行分位数回归
对于使用MATLAB、R语言或者STATA执行带有虚拟变量的分位数回归,这三个工具都带有强大的分析功能。在核心观点上,首先需要理解分位数回归的基本原理、其次要掌握如何在各个统计软件中实现该分析、最后,需要熟悉虚拟变量在模型中的应用并合理加…...
[操作系统,学习记录]3.进程(2)
1.fork(); 玩法一:通过返回值if,else去执行不同的代码片段 玩法二:if,else然后调用execve函数去执行新的程序 2.进程终止: 退出码,子进程通过exit/return返回,父进程wait/waitpid等待而得&am…...
26考研——排序_选择排序_选择排序的基本思想 简单选择排序(8)
408答疑 文章目录 四、选择排序选择排序的基本思想简单选择排序定义算法思想性能分析空间效率时间效率稳定性 适用性 九、参考资料鲍鱼科技课件26王道考研书 四、选择排序 选择排序的基本思想 每一趟(如第 i i i 趟)在剩下 n − i 1 n-i1 n−i1&…...
StarRocks BE宕机排查
StarRocks BE宕机排查 排查是否OOM dmesg -T|grep -i oom #排查是否oom原因: 2.X版本OOM原因 BE 的配置文件 (be.conf) 中 mem_limit 配置不合理,需要配置mem_limit(机器总内存-其他服务占用内存-1~2g(系统预留)) 比如机器内存40G,上面有…...
PPT——组合SCI论文图片
SCI论文中对于图的排版常常是最头疼的事情,通常需要几个图组合在一起,并且如何控制图中的字体一致也是麻烦事。 保持这个大图里面的一致,转头一看跟其他图又不一致了。最近跟我的博导学了一手,今天就来记录一下吧。主要用到的软件…...
Tabby 一:如何在Mac配置保姆级教程(本地模型替换hugging face下载)
1. brew安装 mac需要先安装brew,如果本地已经安装过brew这一步可以忽略,遇到问题可以自己ai问 /bin/bash -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 可能遇到source .zprofile失败,因为…...
03 相机标定图像采集
学完本文,您将获取一下技能: 1:如何提升标定质量,如选择标定板,标定图像采集的注意事项, 2:实现标定图像自动筛选的代码 3:量产场景如何通过一张图像来标定相机 为了实现良好的标定效果,以下因素在标定数据采集前必须设置得当。 标定板选择 标定板尺寸准确材料平…...
CentOS 安装LAMP全过程 - 完整步骤与最佳实践
在开始搭建 LAMP 环境之前,需要确保系统已经满足以下条件: 1、操作系统:CentOS 7 或 CentOS 8 2、网络连接:系统必须能够访问互联网以下载所需的软件包 3、权限:需要 root 权限或者通过sudo 提权执行命令 先更新系…...
基于DCT变换和Huffman编码的图像压缩解压缩算法matlab性能仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 DCT变换 4.2 Huffman编码的数学原理 4.3 图像压缩流程 4.4 仿真测试指标 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a/matl…...
Vue下 Sortable 实现 table 列表字段可拖拽排序,显示隐藏组件开发
vue 开发table 列表时,需要动态调整列字段的顺序和显示隐藏 实现效果如图所示: vue 组件代码 <template><div style"width: 90%; margin: 0 auto;"><el-table :data"tableData" border"" ref"table…...
李飞飞、吴佳俊团队新作:FlowMo如何以零卷积、零对抗损失实现ImageNet重构新巅峰
目录 一、摘要 二、引言 三、相关工作 四、方法 基于扩散先前的离散标记化器利用广告 架构 阶段 1A:模式匹配预训练 阶段 1B:模式搜索后训练 采样 第二阶段:潜在生成建模 五、Coovally AI模型训练与应用平台 六、实验 主要结果 …...
.js项目编译成.exe程序(交叉编译全过程整理)
1.前提: (这个文档有配套的视频解说教程,大家想看的话, 直接在 blibli搜索 , 尘埃落在星河湾 这个up主, 将vtk.js打包成.exe实录课程_哔哩哔哩_bilibili ) 先将下载库的环境搭建好: 打开编辑配置文件,下面代码依次执行 npm config edit registryhttps://registry.npmmirror.com…...
Android面试总结之Handler 机制深入探讨原理、应用与优化
1.使用过Handler吗?Handler是用来干嘛的? 答:使用过,Handler是android中设计的用于线程间通信的工具类,针对单个Handler对象而言,与其关联的有Message,MessageQueue和Looper,其中Me…...
OpenAI 推出图像生成新突破:GPT-4o 实现图像编辑对话化
关键要点 OpenAI 推出了 4o 图像生成功能,集成于 GPT-4o,提供精准且逼真的图像生成。 它似乎适用于多种用户,包括免费用户,API 访问预计几周内推出。 安全措施包括 C2PA 元数据和内容屏蔽,限制生成不适当图像。 研究…...
android11关机安卓充电的UI定制化
引言 首先上一张安卓充电的图片: 安卓关机状态下有两种充电模式:uboot-charge和android-charge,可通过dts配置使用哪一种充电模式。 dts配置中uboot-charge和android-charge是互斥的,如下配置的是开启android-charge:…...
Web前端之JavaScript的DOM操作冷门API
MENU 前言1、Element.checkVisibility()2、TreeWalker3、Node.compareDocumentPosition()4、scrollIntoViewIfNeeded()5、insertAdjacentElement()6、Range.surroundContents()7、Node.isEqualNode()8、document.createExpression()小结 前言 作为前端开发者,我们每…...
惠普(HP)和联想(Lenovo)作为全球两大电脑品牌,并不是简单的“拼接电脑”
惠普(HP)和联想(Lenovo)作为全球两大电脑品牌,并不是简单的“拼接电脑”,它们都有自己的核心技术、专利设计和生态体系。以下是它们“自己的”核心部分: 1. 关键自研技术 品牌自研技术/专利说明…...
集成开发环境革新:IntelliJ IDEA与Cursor AI的智能演进
集成开发环境革新:IntelliJ IDEA 与 Cursor AI 的智能演进 集成开发环境(IDE) 是软件开发者必不可少的工具。一个优秀的 IDE 不仅能够帮助编写和调试代码,还能集成版本控制和代码优化等多种功能。如今,随着人工智能&a…...
EXCEL报错:无法共享此工作薄,因表包含excel表或xml映射的解决方法
在分享工作薄是,如果出现了“无法共享此工作薄,因表包含excel表或xml映射”的报错,那么有两个原因: 1.包含Excel表格,这个也是相对比较常见的原因。 首先选中表格。如果你不知道表的位置在哪,那么在Excel左…...
《深度剖析SQL之WHERE子句:数据过滤的艺术》
在当今数据驱动的时代,数据处理和分析能力已成为职场中至关重要的技能。SQL作为一种强大的结构化查询语言,在数据管理和分析领域占据着核心地位。而WHERE子句,作为SQL中用于数据过滤的关键组件,就像是一把精准的手术刀,…...
【悲观锁和乐观锁有什么区别】以及在Spring Boot、MybatisPlus、PostgreSql中使用
悲观锁和乐观锁是两种常见的并发控制方式,它们在处理并发数据访问时的策略和实现方式有很大的不同。下面是这两者的主要区别: 1. 锁的策略 悲观锁(Pessimistic Locking): 假设并发冲突频繁发生,因此在操作…...
《Linux运维实战:Ubuntu 22.04配置pam实现密码复杂度策略》
总结:整理不易,如果对你有帮助,可否点赞关注一下? 更多详细内容请参考:Linux运维实战总结 一、背景信息 由于安全方面的考虑,先要求Ubuntu 22.04系统需配置密码复杂度策略,先要求如下࿱…...
域名解析:从基础概念到安全风险全面指南
目录 什么是域名? 域名在哪里注册? 域名层级解析:二级与多级域名 域名发现对安全测试的意义 二到多级域名面临的网络安全风险 如何加强域名安全管理 总结 什么是域名 域名(Domain Name)是互联网上用于标识和定位计算机、网络服务的字…...
从代码学习深度学习 - 使用块的网络(VGG)PyTorch版
文章目录 前言一、VGG网络简介1.1 VGG的核心特点1.2 VGG的典型结构1.3 优点与局限性1.4 本文的实现目标二、搭建VGG网络2.1 数据准备2.2 定义VGG块2.3 构建VGG网络2.4 辅助工具2.4.1 计时器和累加器2.4.2 准确率计算2.4.3 可视化工具2.5 训练模型2.6 运行实验总结前言 深度学习…...
Java课程设计(双人对战游戏)持续更新......
少废话,当然借助了ai,就这么个实力,后续会逐渐完善...... 考虑添加以下功能: 选将,选图,技能,天赋,道具,防反,反重力,物理反弹,击落…...
Windows 安装多用户和其它一些问题 VMware Onedrive打不开
以下以win10家庭版为例,win11、专业版类似。 Onedrive相关问题参看我的其他文章: Windows如何同时登录两个OneDrive个人版账号_onedrive登录两个账号-CSDN博客 win10 win11 设置文件权限以解决Onedrive不能同步问题_onedrive没有同步权限-CSDN博客 O…...
深入解析:MySQL 中 NULL 值是否占用 1 bit 存储空间?
在 MySQL 的存储机制中,关于 NULL 值是否占用 1 bit 的存储空间,存在一个常见的理解误区。许多人认为“每个 NULL 值占用 1 bit”,但这并不完全准确。本文将通过 InnoDB 引擎的存储原理,详细解释 NULL 值的实际存储开销,并澄清这一误解。 一、核心结论 允许为 NULL 的列会…...
java基础自用笔记:异常、泛型、集合框架(List、Set、Map)、Stream流
异常 异常体系 编译时异常代表程序觉得你可能会出错。 运行时异常代表已经出错 异常基本处理 异常的作用 可以在可能出现的异常的地方用返回异常来代替return,这样提醒程序出现异常简洁清晰 自定义异常 最好用运行时异常,不会像编译时异常那样烦人&a…...
