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/notifyCondition
锁的管理隐式锁(通过 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方法来通知它条件已经满足。

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

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

相关文章

29_项目

目录 http.js 1、先注册账号 register.html 2、再登录 login.html 3、首页 index.html 4 详情 details.html cart.html css index.css register.css details.css 演示 进阶 http.js let baseURL "http://localhost:8888"; let resgiterApi baseURL &…

Next.js 项目生产构建优化

Next.js 项目生产构建优化的完整教程&#xff0c;涵盖配置、工具链和性能调优技巧&#xff0c;助你显著加速 npm run build&#xff1a; 注&#xff1a;学习阶段请先测试环境使用&#xff01; 文章目录 [toc]一、基础优化1. 确保使用最新版本2. 清理无用依赖和代码3. 配置 nex…

【嵌入式学习3】多任务编程

目录 1、多任务 并发&#xff1a;在一段时间内交替去执行任务 并行&#xff1a; 2、线程 进程与线程 守护线程 1、多任务 在同一时间内执行多个任务&#xff0c;多任务分为并发和并行两种形式 并发&#xff1a;在一段时间内交替去执行任务 软件1执行0.01秒&#xff0c;切…

链路聚合(Link Aggregation)

目录 一. 链路聚合概述 1. 基本概念 2. 实现条件 3. 成员接口和成员链路 二. 链路聚合模式 1. 手工模式 2. LACP模式 三. 负载分担 1. 基于数据包的负载分担 2. 基于数据流的负载分担 一. 链路聚合概述 1. 基本概念 链路聚合&#xff08;Link Aggregation&#xff…

QT图片轮播器(QT实操学习2)

1.项目架构 1.UI界面 2.widget.h​ #ifndef WIDGET_H #define WIDGET_H#include <QWidget>#define TIMEOUT 1 * 1000 QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent n…

【HTML5】02-列表 + 表格 + 表单

本文介绍 列表、表格、表单的具体使用。 目录 1. 列表 1.1 无序列表 1.2 有序列表 1.3 定义列表 2. 表格 2.1 基本使用 2.2 表格结构标签 2.3 合并单元格 3. 表单 3.1 input标签 3.2 input 标签占位文本 3.3 单选框 3.4 上传文件 3.5 多选框 3.6 下拉菜单 3.7…

【数据结构】导航

【数据结构】-CSDN博客 【数据结构】next数组、nextval数组-CSDN博客

开源项目里的 autogen.sh 是做什么?

./autogen.sh 是一个在基于 Autotools 构建系统的开源项目中常见的脚本。它的主要作用是准备构建环境&#xff0c;生成后续编译所需的关键文件。 更具体地说&#xff0c;./autogen.sh 通常会执行以下操作&#xff1a; 检查构建工具: 它会检查系统中是否安装了构建项目所需的工…

RabbitMQ高级特性--发送方确认

目录 1. confirm确认模式 1.配置RabbitMQ 2.设置确认回调逻辑并发送消息 2.Return退回模式 1.配置RabbitMQ 2.设置返回回调逻辑并发送消息 在使用RabbitMQ的时候, 可以通过消息持久化来解决因为服务器的异常崩溃而导致的消息丢失, 但是还有⼀个问题, 当消息的生产者将消息发送出…

Python的ASGI Web 服务器之uvicorn

文章目录 什么是uvicornUvicorn 和 uWSGI 对比区别安装 Uvicorn使用示例 什么是uvicorn 官网https://www.uvicorn.org/ Uvicorn 是一个用于 Python 的 ASGI Web 服务器实现。 Until recently Python has lacked a minimal low-level server/application interface for async…

MongoDB 创建数据库

MongoDB 创建数据库 引言 MongoDB 是一款高性能、可扩展的 NoSQL 数据库&#xff0c;广泛应用于大数据领域。在 MongoDB 中&#xff0c;创建数据库是进行数据存储的第一步。本文将详细介绍 MongoDB 数据库的创建方法&#xff0c;包括手动创建和自动创建两种方式。 MongoDB 数…

并发编程之最小化共享

文章目录 **什么是「最小化共享」&#xff1f;****为什么要最小化共享&#xff1f;****如何实现最小化共享&#xff1f;****1. 线程局部存储&#xff08;Thread-Local Storage&#xff09;****2. 消息传递&#xff08;Message Passing&#xff09;****3. 不可变数据&#xff08…

通信之光纤耦合器

以下是关于光纤耦合器的详细介绍&#xff1a; 定义与原理 - 定义&#xff1a;光纤耦合器是一种能使传输中的光信号在特殊结构的耦合区发生耦合&#xff0c;并进行再分配的器件&#xff0c;也叫分歧器、连接器、适配器、光纤法兰盘。 - 原理&#xff1a;利用不同光纤面紧邻光纤芯…

自然语言模型的演变与未来趋势:从规则到多模态智能的跨越

自然语言模型的演变与未来趋势&#xff1a;从规则到多模态智能的跨越 自然语言处理(NLP)作为人工智能领域最具挑战性的分支之一&#xff0c;在过去几十年经历了翻天覆地的变化。从最初基于规则的系统到如今拥有万亿参数的大型语言模型(LLMs)&#xff0c;这一技术革新不仅彻底改…

笔记本电脑更换主板后出现2203:System configuration is invalid,以及2201、2202系统错误的解决

笔记本电脑更换主板后启动出现2203:System configuration is invalid,以及2201、2202系统错误的解决 自用的一台ThinkpadT490笔记本电脑 ,由于主板故障,不得不更换主板,通过某宝购置主板后进行了更换。 具体拆卸笔记本可搜索网络视频教程。 注意: 在更换主板时,注意先拍…

JavaScript中的观察者模式

以下是关于 观察者模式(Observer Pattern) 的全面梳理,涵盖核心概念、实现方式、应用场景及注意事项,帮助我们掌握这一解耦事件通知与处理的经典设计模式: 一、观察者模式基础 1. 核心概念 定义:定义对象间 一对多 的依赖关系,当被观察对象(Subject)状态变化时,自动…

RAG基建之PDF解析的“流水线”魔法之旅

将PDF文件和扫描图像等非结构化文档转换为结构化或半结构化格式是人工智能的关键部分。然而,由于PDF的复杂性和PDF解析任务的复杂性,这一过程显得神秘莫测。 在RAG(Retrieval-Augmented Generation)基建之PDF解析的“魔法”与“陷阱”中,我们介绍了PDF解析的主要任务,对现…

【Linux】GDB调试指南

一、GDB基础 1. 启动调试 gdb ./your_program # 启动调试 gdb --args ./prog arg1 # 带参数启动 gdb -p <pid> # 附加到正在运行的进程 2. 断点管理 b main # 在main函数设断点 b file.c:20 # 在file.c第20行设断点 b *0x4005a…

Android面试总结之Glide源码级理解

当你的图片列表在低端机上白屏3秒、高端机因内存浪费导致FPS腰斩时&#xff0c;根源往往藏在Glide的内存分配僵化、磁盘混存、网络加载无优先级三大致命缺陷中。 本文从阿里P8级缓存改造方案出发&#xff0c;结合Glide源码实现动态内存扩容、磁盘冷热分区、智能预加载等黑科技&…