LockSupport与Condition解析

本章我们介绍两个Java 并发包中用于线程协作的工具--LockSupport和Condition

LockSupport:

Java 并发包(java.util.concurrent.locks)提供了基于许可(permit)的线程阻塞和唤醒机制--LockSupport

对于LockSupport是通过方法park以及unpark来对线程进行阻塞和唤醒的

    public static void park() {UNSAFE.park(false, 0L);}public static void unpark(Thread var0) {if (var0 != null) {UNSAFE.unpark(var0);}}

我们可以看出park方法很简单只是调用了Unsafe 的方法park。Unsafe 正如它名字而言是 Java 中一个 高度危险且未被官方正式支持 的类,位于 sun.misc 包下(JDK 9 后移至 jdk.internal.misc 包)。它提供了一系列 直接操作底层资源 的方法,允许开发者绕过 Java 语言的安全机制,直接访问内存、操作线程状态等。所以LockSupport就是基于unsafe类进行包装后的类,将原来的不安全类封装成了一个安全类供开发者使用。对于park方法的两个参数一个是Boolean类型一个是long类型,分别用来表示是否为绝对时间以及阻塞的时长,对于0L则是永久阻塞。

对于unpark方法来说则是多了一个参数Thread,这个参数的作用是用来指定唤醒的线程。为什么park不需要参数而unpark需要参数呢,因为unpark唤醒的都是其他线程,当本线程进入阻塞后则无法自己唤醒自己只能通过其他线程来唤醒自己。

Condition:

Condition 是 Java 并发包(java.util.concurrent.locks)中的一个接口,用于替代传统的 Object.wait()Object.notify() 和 Object.notifyAll(),提供更灵活、更强大的线程间协作机制。它通常与 Lock 接口配合使用,实现精细化的线程等待和唤醒操作。

也就是说Condition的定位其实与Object.wait类似,都是协助锁来实现线程的协作机制。

特性ConditionObject.wait()/notify()
锁机制必须与 Lock 显式关联(如 ReentrantLock)。必须在 synchronized 块中调用。
等待队列每个 Condition 独立维护一个等待队列,可创建多个条件队列(如 notFullnotEmpty)。每个对象只有一个等待队列,所有线程共享。
唤醒方式signal():唤醒一个等待线程;
signalAll():唤醒所有等待线程。
notify():随机唤醒一个线程;
notifyAll():唤醒所有线程。
中断支持await() 可响应中断(抛出 InterruptedException),也支持不可中断模式(awaitUninterruptibly())。wait() 只能响应中断(抛出异常),无法禁用。
超时机制支持灵活的超时等待(如 await(long, TimeUnit))。仅支持 wait(long timeout)(毫秒级)。

其中Condition接口的实现类则是在AQS内部中,而AQS则赋予了Condition灵魂,下面我们来看看Condition子类ConditionObject的源码。

在ConditionObject中我们从Condition最核心的两个方法await方法和signal方法来说起

public final void await() throws InterruptedException {if (Thread.interrupted()) {throw new InterruptedException();} else {Node var1 = this.addConditionWaiter();long var2 = AbstractQueuedLongSynchronizer.this.fullyRelease(var1);int var4 = 0;while(!AbstractQueuedLongSynchronizer.this.isOnSyncQueue(var1)) {LockSupport.park(this);if ((var4 = this.checkInterruptWhileWaiting(var1)) != 0) {break;}}if (AbstractQueuedLongSynchronizer.this.acquireQueued(var1, var2) && var4 != -1) {var4 = 1;}if (var1.nextWaiter != null) {this.unlinkCancelledWaiters();}if (var4 != 0) {this.reportInterruptAfterWait(var4);}}
}

首先则是线程中断位的判断如果已经中断则直接抛出异常(由此可以看出await方法是可中断的方法)。随后则是调用了addConditionWaiter方法,这个方法的作用就是将当前线程加入到Condition所维持的队列中,在我们解析addConditionWaiter方法之前先看一下Condition的属性

    public class ConditionObject implements Condition, Serializable {private static final long serialVersionUID = 1173984872572414699L;private transient Node firstWaiter;private transient Node lastWaiter;private static final int REINTERRUPT = 1;private static final int THROW_IE = -1;。。。。}

可以看出含有两个属性firstWaiter和lastWaiter,这两个属性分别表示的是Condition所维持的链表的表头和表尾,由此我们又可以看出ConditionObject 则是用一个链表来串联起整个队列的。

然后我们开始进入addConditionWaiter方法中来看看是如何加入队列的

        private Node addConditionWaiter() {Node var1 = this.lastWaiter;if (var1 != null && var1.waitStatus != -2) {this.unlinkCancelledWaiters();var1 = this.lastWaiter;}Node var2 = new Node(Thread.currentThread(), -2);if (var1 == null) {this.firstWaiter = var2;} else {var1.nextWaiter = var2;}this.lastWaiter = var2;return var2;}

代码可以看出首先将队尾的Node节点取出并且检查状态,如果状态不符合要求则会清除出队列。

之后开始初始化本线程的Node节点,并且放在队尾后面。好了这个方法的大致功能已经捋顺接下来我们看看下面的方法

                long var2 = AbstractQueuedLongSynchronizer.this.fullyRelease(var1);int var4 = 0;while(!AbstractQueuedLongSynchronizer.this.isOnSyncQueue(var1)) {LockSupport.park(this);if ((var4 = this.checkInterruptWhileWaiting(var1)) != 0) {break;}}if (AbstractQueuedLongSynchronizer.this.acquireQueued(var1, var2) && var4 != -1) {var4 = 1;}if (var1.nextWaiter != null) {this.unlinkCancelledWaiters();}if (var4 != 0) {this.reportInterruptAfterWait(var4);}

随后就会调用AQS的方法进行锁释放(由于线程已经进入Condition队列中),随后则会一个while循环调用isOnSyncQueue进行校验保证当前Node节点不会在AQS的同步队列,这时候就会有疑惑了为什么要保证不会在AQS的同步队列呢,原因就是当Condition调用signal方法的时候被唤醒的线程会从Condition队列中移除转而放入到AQS维护的CLH同步队列中去。所以这里的循环查看是否在同步队列换个意思就是保证当前Node节点没有被唤醒。

在保证当前没有被唤醒之后则会调用 LockSupport.park(this)来讲当前的线程阻塞。由此可见LockSupport很纯粹也很底层,目的就是为了将当前线程进行阻塞或者唤醒。在进入阻塞之后后续的代码则不会执行而是等到唤醒之后才会执行。

唤醒之后首先查看当前线程的中断位,如果被中断则直接跳出循环。

随后则参与锁的竞争调用了acquireQueued方法表示来占有锁,占有锁成功之后则会执行后续的方法如果后面还有节点那么会遍历清除后面的节点。

最后进行判断如果中断位是否开启并且进行处理中断位

下面举一个常用示例

// 示例:生产者-消费者模型
lock.lock();
try {while (queue.isFull()) {notFull.await(); // 等待队列不满}// await() 返回后,线程已持有锁,继续执行queue.add(item); // 生产元素notEmpty.signal(); // 通知消费者
} finally {lock.unlock();
}

整体流程:

  1. 开始 → 检查线程中断(若已中断,抛异常)
  2. 创建节点加入 Condition 队列 → 释放锁
  3. 循环检查节点是否在同步队列
    •  → 调用park()阻塞 → 等待唤醒 / 中断
    •  → 跳出循环
  4. 重新竞争锁acquireQueued)→ 获取锁后处理中断标记
  5. 清理 Condition 队列无效节点 → 根据中断状态处理异常或恢复标志
  6. 方法返回,线程持有锁继续执行后续逻辑

下一章节我们将把最后的signal函数源码解析给讲述完毕

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

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

相关文章

【机器学习基础】机器学习入门核心算法:逻辑回归(Decision Tree)

机器学习入门核心算法:逻辑回归(Decision Tree) 一、算法逻辑1.1 基本概念1.2 算法流程 二、算法原理与数学推导2.1 特征选择指标信息熵(ID3算法)信息增益(Information Gain)信息增益率&#xf…

网络编程3

管道的性质 读缓冲区为空,read阻塞写缓冲区为空,write阻塞一端先close,另一端继续read,read不阻塞,立刻返回0一端先close,另一端继续write,write会触发SIGPIPE信号,进程异常终止 soc…

influxdb时序数据库

以下概念及操作均来自influxdb2 官方文档 InfluxDB2 is the platform purpose-built to collect, store, process and visualize time series data. Time series data is a sequence of data points indexed in time order. Data points typically consist of successive meas…

洛谷 P3372 【模板】线段树 1

【题目链接】 洛谷 P3372 【模板】线段树 1 【题目考点】 1. 线段树 2. 树状数组 【解题思路】 本题要求维护区间和,实现区间修改、区间查询。 可以使用树状数组或线段树完成该问题,本文仅介绍使用线段树的解法。 解法1:线段树 线段树…

软件更新 | TSMaster 202504 版本已上线!三大功能让车载测试更智能

车载测试的智能化时代正在加速到来!TSMaster 202504 版本正式发布,本次更新聚焦以太网通信与数据高效处理,带来三大核心功能升级—以太网报文信息过滤、XCP on Ethernet支持、按时间范围离线回放,助力工程师更精准、更灵活地完成测…

java-单列集合list与set。

集合定位:存储数据的容器 与数组的区别: 数组只能存储同种数据类型数据,集合可以存储不同类型的数据。 数组的长度一旦创建长度不可变,集合的长度是可变的 数组的操作单一,集合的操作比较丰富(增删改查&…

ai之pdf解析工具 PPStructure 还是PaddleOCR

目录 重点是四 先用 PPStructure 版面分析,分成不同的块儿,再选用 PaddleOCR、或PPStructure基础路径OCR模型配置OCR模型配置GPU配置硬件配置性能配置一、框架选型对比分析1. **PaddleOCR核心能力**2. **PP-Structure核心能力**3. **选型结论**二、错误根因分析与修复方案1. …

Android计算机网络学习总结

TCP vs UDP 核心区别​​ ​题目​:TCP为什么称为可靠传输协议?UDP在哪些场景下比TCP更具优势? ​得分要点​: ​可靠性机制​ 三握四挥建立可靠连接确认应答(ACK) 超时重传滑动窗口流量控制拥塞控制&…

深入解析Java组合模式:构建灵活树形结构的艺术

引言:当对象需要树形组织时 在日常开发中,我们经常需要处理具有层次结构的对象集合。比如: 文件系统中的文件夹与文件GUI界面中的容器与控件企业组织架构中的部门与员工 这类场景中的对象呈现明显的整体-部分层次结构,如何优雅…

mobaxterm通过ssh登录docker无图形界面

1. 流程 下面是使用Mobaxterm通过SSH登录Docker无图形界面的步骤: 步骤 操作 1 在本地安装Mobaxterm 2 配置Mobaxterm连接SSH 3 启动Docker容器 4 在Mobaxterm中通过SSH连接到Docker容器 2. 操作步骤 步骤1:安装Mobaxterm 首先&#xff…

【赵渝强老师】HBase的体系架构

HBase是大表(BigTable)思想的一个具体实现。它是一个列式存储的NoSQL数据库,适合执行数据的分析和处理。简单来说,就是适合执行查询操作。从体系架构的角度看,HBase是一种主从架构,包含:HBase H…

linux 新增驱动宏config.in配置

‌1. 添加配置宏步骤‌ ‌1.1 修改 Kconfig(推荐方式)‌ ‌定位 Kconfig 文件‌ 内核各子目录(如 drivers/char/)通常包含 Kconfig 文件,用于定义模块配置选项7。‌添加宏定义‌ 示例:在 drivers/char/Kc…

关于git的使用

下载git 可以去git的官网下载https://git-scm.com/downloads 也可以去找第三方的资源下载,下载后是一个exe应用程序,直接点开一直下一步就可以安装了 右键任意位置显示这两个就代表成功,第一个是git官方的图形化界面,第二个是用…

WPF【11_8】WPF实战-重构与美化(UI 与视图模型的联动,实现INotifyPropertyChanged)

11-13 【重构】INotifyPropertyChanged 与 ObservableCollection 现在我们来完成新建客户的功能。 当用户点击“客户添加”按钮以后系统会清空当前所选定的客户,客户的详细信息以及客户的预约记录会从 UI 中被清除。然后我们就可以在输入框中输入新的客户信息了&am…

ArkUI:鸿蒙应用响应式与组件化开发指南(一)

文章目录 引言1.ArkUI核心能力概览1.1状态驱动视图1.2组件化:构建可复用UI 2.状态管理:从单一组件到全局共享2.1 状态装饰器2.2 状态传递模式对比 引言 鸿蒙生态正催生应用开发的新范式。作为面向全场景的分布式操作系统,鸿蒙的北向应用开发…

List优雅分组

一、前言 最近小永哥发现,在开发过程中,经常会遇到需要对list进行分组,就是假如有一个RecordTest对象集合,RecordTest对象都有一个type的属性,需要将这个集合按type属性进行分组,转换为一个以type为key&…

AI与.NET技术实操系列(八):使用Catalyst进行自然语言处理

引言 自然语言处理(Natural Language Processing, NLP)是人工智能领域中最具活力和潜力的分支之一。从智能客服到机器翻译,再到语音识别,NLP技术正以其强大的功能改变着我们的生活方式和工作模式。 Catalyst的推出极大降低了NLP…

MySQL 8.0 OCP 1Z0-908 题目解析(13)

题目49 Choose the best answer. t is a non - empty InnoDB table. Examine these statements, which are executed in one session: BEGIN; SELECT * FROM t FOR UPDATE;Which is true? ○ A) mysqlcheck --analyze --all - databases will execute normally on all ta…

Docker 一键部署倒计时页面:Easy Countdown全设备通用

Easy Countdown 介绍 Easy countdown是一个易于设置的倒计时页面。可以设置为倒计时或计时器。可用于个人生活、工作管理、教育、活动策划等多个领域。 🚢 项目地址 Github:https://github.com/Yooooomi/easy-countdown 🚀Easy Countdown …

Python训练打卡Day35

模型可视化与推理 知识点回顾: 三种不同的模型可视化方法:推荐torchinfo打印summary权重分布可视化进度条功能:手动和自动写法,让打印结果更加美观推理的写法:评估模式 模型结构可视化 理解一个深度学习网络最重要的2点…