Java 入门指南:Java 并发编程 —— Condition 灵活管理线程间的同步

Condition

Condition 是 Java 并发编程中的一种高级同步工具,它可以协助线程之间进行等待和通信。提供了一种比传统的 wait()notify() 更加灵活的方式来管理线程间的同步。Condition 接口通常与 Lock 接口一起使用,允许更细粒度的控制线程的等待和唤醒。

每个 Condition 对象都与一个 Lock 对象相关联,它可以在等待之前获取到锁,并在等待期间自动释放锁。一旦其他线程对相关的数据做出更改并且满足某个特定条件,Condition 就可以被用来通知正在等待的线程,并进行唤醒等待的操作

接口方法

Condition 接口提供了如下方法:

![[UML of Condition.png]]

  • await():线程等待直到其他线程调用相同 Condition 对象的 signal/signalAll 方法,并且当前线程被唤醒;其他线程调用 interrupt 方法中断当前线程。类似于 Object.wait()

  • await(long time, TimeUnit unit):线程等待指定的时间,或被通知,或被中断。类似于 Object.wait(long timeout),但提供了更灵活的时间单位。

  • awaitUninterruptibly():线程等待直到被通知,即使在等待时被中断也不会返回,不响应中断。没有与之对应的 Object 方法。

  • awaitNanos(long nanosTimeout):线程等待指定的纳秒时间,或被通知,或被中断,返回值小于等于0,认定为超时。没有与之对应的 Object 方法。

  • awaitUntil(Date deadline):线程等待直到指定的截止日期,或被通知,或被中断。如果没到指定时间被通知,则返回 true,否则返回 false。没有与之对应的 Object 方法。

  • signal():唤醒一个等待在 Condition 上的线程,被唤醒的线程在方法返回前必须获得与 Condition 对象关联的锁。类似于 Object.notify()

  • signalAll():唤醒所有等待在 Condition 上的线程,能够从 await()等方法返回的线程必须先获得与 Condition 对象关联的锁。类似于 Object.notifyAll()

await()

方法逻辑:

  1. 如果当前线程被中断,抛出 InterruptedException

  2. 通过 getState() 保存锁状态。

  3. 以保存的锁状态作为参数调用 release(),如果失败则抛出IllegalMonitorStateException

  4. 阻塞直到被通知或线程中断。

  5. 通过以保存状态作为参数调用 acquire() 的带参方法来重新获取锁。

  6. 如果在第4步阻塞时中断,则抛出 InterruptedException

![[Condition.await() Queue.png]]

调用 condition.await 方法的线程必须是已经获得了 lock 的线程,也就是当前线程是同步队列中的头节点

当前线程调用condition.await() 方法后,会释放 lock 然后加入到等待队列,直到被 signal/signalAll 方法唤醒。

线程如何被添加到等待队列

await() 中 调用了 addConditionWaiter 方法会将当前线程添加到等待队列中。

将不处于等待状态的节点从等待队列中移除,通过尾插入的方式将当前线程封装的 Node 插入到等待队列中,同时可以看出,Condtion 的等待队列是一个不带头节点的链式队列

释放锁的过程

将当前节点插入到等待对列之后,会使当前线程释放 lock,由 fullyRelease 方法实现

首先获取锁的状态,调用 AQS 的模板方法 release 释放 AQS 的同步状态,并且唤醒在同步队列中头节点的后继节点引用的线程,如果释放成功则正常返回,若失败,抛出 IllegalMonitorStateException,并将节点状态设置为 CANCELLED

退出 await()

退出 await 方法的前提条件是当前线程被中断或者调用 condition.signal() 或者 condition.signalAll() 使当前节点移动到同步队列后。

当退出 while 循环后会调用 acquireQueued(node, savedState),该方法的作用是在自旋过程中线程不断尝试获取同步状态,直到成功(线程获取到 lock)。这样也说明了退出 await 方法必须是已经获得了 condition 引用(关联)的 lock

signal()

调用 condition 的 signal() 或者 signalAll() 方法可以将等待队列中等待时间最长的节点移动到同步队列中,使得该节点能够有机会获得 lock。

signal() 方法源码
![[ConditionObject.signal().png]]
doSignal() 方法源码
![[ConditionObject.doSignal.png]]

调用 condition.signal 方法的前提条件是当前线程已经获取了 lock,若未获得 lock,会直接抛出 IllegalMonitorStateException

等待队列是先进先出(FIFO)的,所以等待队列的头节点必然会是等待时间最长的节点,也就是每次调用 condition 的 signal 方法都会将头节点的状态设置为 CONDITION,并移动到同步队列中

![[ConditionObject.signal() Process.png]]

signalAll()

signalAllsignal 方法的区别体现在 doSignalAll 方法上,该方法会将等待队列中的每一个节点都移入到同步队列中,即“通知”当前调用 condition.await() 方法的每一个线程。

await() 与 signal()

awaitsignalsignalAll 方法就像一个开关,控制着线程 A(等待方)和线程 B(通知方)

![[Condition working process.png]]

  • 线程 awaitThread 先通过 lock.lock() 方法获取锁,成功后调用 condition.await 方法进入等待队列

  • 另一个线程 signalThread 通过 lock.lock() 方法获取锁成功后调用了 condition.signal 或者 signalAll 方法,使得线程 awaitThread 能够有机会移入到同步队列中.

  • 当其他线程释放 lock 后使得线程 awaitThread 能够有机会获取 lock,从而使得线程 awaitThread 能够从 await 方法中退出并执行后续操作。

  • 如果 awaitThread 获取 lock 失败会直接进入到同步队列。

创建 Condition 对象

可以通过 lock.newCondition() 来创建一个 Condition 对象,这个方法实际上会 new 一个 ConditionObject 的对象,ConditionObject 是 [[AQS、AQLS、AOS]] 的一个内部类

![[ReentrantLock newCondition.png]]

AQS 内部维护了一个 FIFO 的双端队列,Condition 内部也使用了同样的方式,内部维护了一个先进先出(FIFO)的单向队列,称为等待队列

![[Queue of Condition.png]]

![[Condition Queue Node.png]]

await 方法的线程都会加入到等待队列中,并且线程状态均为等待状态。firstWaiter 指向首节点,lastWaiter 指向尾节点

Node 中的 nextWaiter 指向队列中的下一个节点。并且进入到等待队列的 Node 节点状态都会被设置为 CONDITION

如果该线程能够从 await() 方法返回的话,一定是该线程获取了与 Condition 相关联的锁。

![[AQS  And Condition Queue.png]]

可以多次调用 newCondition() 方法创建多个 Condition 对象,也就是一个 lock 可以持有多个等待队列

使用多个 Condition 对象的主要优点是为锁提供了更细粒度的控制,可以实现更复杂的同步场景。

Object.monitor 和 Condition

每个对象都可以用 Objectwait()notify() 方法来实现等待/通知机制。而 Condition 接口也提供了类似 Object 的方法 await() signal(),可以配合Lock 来实现等待/通知模式。****

![[Object.monitor Condition Comparison.png]]

相似之处
  1. 线程等待Conditionawait() 方法和 Objectwait() 方法都可以使当前线程进入等待状态,直到被其他线程唤醒。

  2. 线程唤醒

    • Conditionsignal() 方法和 Objectnotify() 方法都可以唤醒一个等待的线程。
    • ConditionsignalAll() 方法类似于 ObjectnotifyAll() 方法,可以唤醒所有等待的线程。
不同之处
  1. 锁的显式管理

    • Condition 必须与 Lock 结合使用,这意味着开发者需要显式地管理锁的获取和释放。这使得代码结构更加清晰,可以避免一些常见的锁使用错误。
    • Objectwaitnotify 方法则隐含地使用了对象的内置锁(即 synchronized 语句块或方法所持有的锁)。
  2. 多个条件变量Condition 允许在同一把锁上创建多个条件变量,这意味着可以在一个锁对象上实现多个不同的等待条件,而 Objectwait/notify 方法只能在单个对象上实现单一的等待条件。

  3. 更细粒度的控制

    • Condition 提供了更多控制选项,例如 awaitNanosawaitUntil 等方法,可以设置等待的超时时间,这使得线程可以在指定时间内等待,而不是无限期地等待。

    • Objectwait 方法虽然也有超时参数,但在使用上不如 Condition 灵活。

  4. 异常处理

    • Conditionawait 方法在等待过程中可能会被中断,从而抛出 InterruptedException。此时,开发人员可以选择是否恢复中断状态。

    • Objectwait 方法同样会在线程被中断时抛出 InterruptedException,但恢复中断状态需要手动设置。

Condition 实现生产者消费者模式

下面是一个使用 Condition 实现的生产者消费者模式的例子,该模式常用于线程间的同步问题。

仓库类

首先,我们定义一个仓库类,它包含一个生产者使用的 put 方法和消费者使用的 take 方法。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Warehouse {private final int maxSize;private int size;private final Lock lock = new ReentrantLock();private final Condition notFull = lock.newCondition();private final Condition notEmpty = lock.newCondition();public Warehouse(int maxSize) {this.maxSize = maxSize;this.size = 0;}public void put(int item) throws InterruptedException {lock.lock();try {// 如果仓库已满,等待直到有空间可用while (size == maxSize) {notFull.await();}// 添加物品size++;System.out.println("Produced item: " + item);// 唤醒等待的消费者notEmpty.signal();} finally {lock.unlock();}}public int take() throws InterruptedException {lock.lock();try {// 如果仓库为空,等待直到有物品可用while (size == 0) {notEmpty.await();}// 移除物品size--;int item = ThreadLocalRandom.current().nextInt(100);System.out.println("Consumed item: " + item);// 唤醒等待的生产者notFull.signal();return item;} finally {lock.unlock();}}
}

生产者和消费者线程

接下来,我们创建生产者和消费者的线程类。

public class Producer implements Runnable {private final Warehouse warehouse;public Producer(Warehouse warehouse) {this.warehouse = warehouse;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {try {warehouse.put(i);Thread.sleep(1000); // 模拟生产过程} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("Producer interrupted");}}}
}public class Consumer implements Runnable {private final Warehouse warehouse;public Consumer(Warehouse warehouse) {this.warehouse = warehouse;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {try {warehouse.take();Thread.sleep(1500); // 模拟消费过程} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("Consumer interrupted");}}}
}

主函数

最后,我们在主函数中创建生产者和消费者线程,并启动它们。

public class Main {public static void main(String[] args) {Warehouse warehouse = new Warehouse(5);Thread producerThread = new Thread(new Producer(warehouse), "Producer");Thread consumerThread = new Thread(new Consumer(warehouse), "Consumer");producerThread.start();consumerThread.start();}
}

示例说明

  1. Lock 和 Condition:

    • 使用 ReentrantLock 创建一个可重入锁。
    • 通过 lock.newCondition() 创建两个 Condition 对象,分别用于控制生产者和消费者的等待条件。
  2. 生产者:

    • put 方法中,当仓库已满时,生产者将等待 notFull 条件被信号唤醒。
    • 生产者添加物品后,唤醒等待的消费者。
  3. 消费者:

    • take 方法中,当仓库为空时,消费者将等待 notEmpty 条件被信号唤醒。
    • 消费者移除物品后,唤醒等待的生产者。

通过这种方式,生产者和消费者之间可以很好地协同工作,避免了死锁的风险,并且提供了更灵活的线程控制手段。

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

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

相关文章

Python 从入门到实战4(序列的操作)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们通过举例学习了python 中列表的简单操作&#xff0c;…

Android CCodec Codec2 (六)C2InterfaceHelper

通过前面几篇文章的学习&#xff0c;我们知道了Codec2参数结构&#xff0c;以及如何定义一个Codec2参数。接下来的几篇文章我们将简单了解上层是如何请求组件支持的参数、如何配置参数&#xff0c;以及参数是如何反射给上层的。本篇文章我们将了解接口参数实例化。 1、C2Interf…

SprinBoot+Vue社团管理系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…

【最全深度学习介绍】基本概念、类型、应用、优缺点、与机器学习区别是什么?

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发…

excel规划求解结合vba宏笔记

目录 概念与配置 规划求解定义 excel设置规划求解 宏的基本操作 excel批量进行规划求解案例 加载规划求解模块 宏的设置 宏录制vba 其他案例 概念与配置 规划求解定义 运用“规划求解”定义并求解问题 - Microsoft 支持 excel设置规划求解 EXCEL规划求解的简明教程…

HarmonyOS鸿蒙开发:在线短视频流畅切换最佳实践

简介 为了帮助开发者解决在应用中在线短视频快速切换时容易出现快速切换播放时延过长的问题&#xff0c;将提供对应场景的解决方案。 该解决方案使用&#xff1a; 视频播放框架AVPlayer和滑块视图容器Swiper进行短视频滑动轮播切换。绘制组件XComponent的Surface类型动态渲染…

midwayjs 框架使用 rabbitmq 消息延迟

插件rabbitmq_delayed_message_exchange是RabbitMQ官方提供的一种用于实现延迟消息的解决方案。该插件将交换机类型扩展至x-delayed-message&#xff0c;这种类型的交换机能够将消息暂时挂起&#xff0c;直到设定的延迟时间到达&#xff0c;才将消息投递到绑定的队列中。这一特…

js做一个带模糊搜索、自动补全的select组件auto-input-select

效果图&#xff1a; 思路 原本是想弄一个输入框input&#xff0c;挡在原生select的前面&#xff0c;结果发现&#xff0c;原生select无论怎么弄&#xff0c;都无法js手动控制展开下拉选&#xff0c;必须点击select&#xff0c;这就很尴尬 然后就只能弄一个输入框input&#x…

python-变量声明、数据类型、标识符

一.变量 1.什么是变量 为什么需要变量呢&#xff1f; 一个程序就是一个世界&#xff0c;不论使用哪种高级程序语言编写代码&#xff0c;变量都是其程序的基本组成单位。如下图所示的sum和sub都是变量。 变量的定义&#xff1a; 变量相当于内存中一个数据存储空间的表示&#…

【Unity小工具】Image组件宽度、高度自适应

Unity开发中&#xff0c;用同一个Image进行动态加载不同尺寸的图片&#xff0c;在显示上会有形变此工具可以进行Image的宽度、高度自适应 实现原理 获取Image原始尺寸&#xff08;sizeDelta&#xff09;获取图片原始尺寸&#xff08;spriteSizeDelta&#xff09;公式&#xff…

Git 忽略已经提交的文件

对于未提交过的文件直接用ignore文件即可,不再赘述 对于已经提交过的文件,但是实际上不需要的,可以用git rm --cached命令 比如下图这个 .vsconfig被我误提交了或者忘了在ignore里添加了 但是我实际上不想要这个文件,那么在项目根目录打开git bash ,输入 git rm --cached .vsc…

【Hot100】LeetCode—34. 在排序数组中查找元素的第一个和最后一个位置

目录 1- 思路二分 - 左侧二分 右侧二分 2- 实现⭐34. 在排序数组中查找元素的第一个和最后一个位置——题解思路 3- ACM 实现 原题链接&#xff1a;34. 在排序数组中查找元素的第一个和最后一个位置 1- 思路 二分 - 左侧二分 右侧二分 右区间二分 ——> 找首次出现的位置…

unreal engine5.4.3动画重定向

UE5系列文章目录 文章目录 UE5系列文章目录前言 前言 ue5.4和ue3动画重定向之间存在差异&#xff0c;跟ue5.2差别更大一点&#xff0c;总之ue5.4越来越简化动画重定向&#xff0c;不想之前还需要制作RTG文件 这是ue5.3.2的制作动画重定向的界面 这是ue5.4.2的制作动画重定向…

编译FFmpeg动态库

编译FFmpeg动态库 环境 macOS High SierraFFmpeg 4.3android-ndk-r21b 编译so库 下载FFmpeg4.3源代码&#xff0c;进入源码目录创建build_android.sh脚本&#xff0c;ffmpeg从4.0起新增了target-osandroid&#xff0c;所以不用再修改configure文件。 注意&#xff1a; ndk…

k8s1.23 部署Prometheus-Operator集群监控

1. Prometheus-Operator介绍 Prometheus Operator 为 Kubernetes 提供了对 Prometheus 相关监控组件的本地部署和管理方案&#xff0c;该项目的目的是为了简化和自动化基于 Prometheus 的监控栈配置&#xff0c;主要包括以下几个功能&#xff1a; kubernetes自定义资源&#…

【网络安全】服务基础第一阶段——第八节:Windows系统管理基础---- Web服务与虚拟主机

目录 一、WWW概述 1.1 HTML 1.2 URI与URL 1.2.1 URL&#xff08;统一资源标识符&#xff0c;Uniform Resource Locator&#xff09; 1.3 HTTP 1.3.1 HTTP请求&#xff1a; 1.3.2 HTTP响应 1.3.3 状态码 1.4常见Web URL格式 实验一、网站搭建 1&#xff09;访问失败可…

孩子自闭症的主要表现:探寻理解之门

自闭症&#xff0c;也称为孤独症&#xff0c;是一种复杂的神经发展障碍&#xff0c;它影响着孩子的社交互动、沟通能力以及行为模式。当家长注意到孩子出现自闭症倾向时&#xff0c;及时识别并寻求专业帮助至关重要。以下是孩子自闭症的一些主要表现&#xff0c;希望能为家长提…

温馨网站练习运用

第二次与团队一起制作网页虽然不进行商用&#xff0c;但是练习一下还是好的&#x1f60a;&#x1f60a; 我主要负责后端部分&#xff0c;该项目用了SpringBoot框架、SpringSecurity的安全框架、结合MyBatis-Plus的数据库查询。如果想看看&#xff0c;网站&#xff1a;温馨网登…

昇腾AI处理器的计算核心 - AI Core即DaVinci Core

昇腾AI处理器的计算核心 - AI Core即DaVinci Core flyfish 从一段代码的解释开始 template <typename T> class GlobalTensor { public:void setGlobalBuffer(T* buffer, uint32_t buffersize) {// 在这里实现设置全局缓冲区的逻辑} };语法的说明&#xff0c;主要用于…

SQLi-LABS靶场51-55通过攻略

第51关&#xff08;报错注入 闭合&#xff09; 查数据库 ?sort1%27%20and%20updatexml(1,concat(1,database()),3)-- 查表 ?sort1 and updatexml(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schemasecurity)),1)-- 第52关…