深入浅出Java Condition 的await和signal机制(二)
Condition 的 await 方法当调用condition.await()方法后会使当前获取锁的线程进入到等待队列如果该线程能够从await()方法返回的话一定是该线程获取了与 Condition 相关联的锁。前面讲过了Condition 只是一个接口它的实现类为 ConditionObject是 AQS 的子类。ConditionObject 的 await 方法源码如下public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 1. 将当前线程包装成Node尾插入到等待队列中 Node node addConditionWaiter(); // 2. 释放当前线程所占用的lock在释放的过程中会唤醒同步队列中的下一个节点 int savedState fullyRelease(node); int interruptMode 0; while (!isOnSyncQueue(node)) { // 3. 当前线程进入到等待状态 LockSupport.park(this); if ((interruptMode checkInterruptWhileWaiting(node)) ! 0) break; } // 4. 自旋等待获取到同步状态即获取到lock if (acquireQueued(node, savedState) interruptMode ! THROW_IE) interruptMode REINTERRUPT; if (node.nextWaiter ! null) // clean up if cancelled unlinkCancelledWaiters(); // 5. 处理被中断的情况 if (interruptMode ! 0) reportInterruptAfterWait(interruptMode); }代码的主要逻辑请看注释。当前线程调用condition.await()方法后会释放 lock 然后加入到等待队列直到被signal/signalAll方法唤醒。怎样将当前线程添加到等待队列调用 addConditionWaiter 方法会将当前线程添加到等待队列中源码如下private Node addConditionWaiter() { Node t lastWaiter; if (t ! null t.waitStatus ! Node.CONDITION) { //将不处于等待状态的节点从等待队列中移除 unlinkCancelledWaiters(); t lastWaiter; } Node node new Node(Thread.currentThread(), Node.CONDITION); //尾节点为空 if (t null) //将首节点指向node firstWaiter node; else //将尾节点的nextWaiter指向node节点 t.nextWaiter node; //尾节点指向node lastWaiter node; return node; }首先将 t 指向尾节点如果尾节点不为空并且它的waitStatus!-2-2 为 CONDITION表示正在等待 Condition 条件则将不处于等待状态的节点从等待队列中移除并且将 t 指向新的尾节点。然后将当前线程封装成 waitStatus 为-2 的节点追加到等待队列尾部。如果尾节点为空则表明队列为空将首尾节点都指向当前节点。如果尾节点不为空表明队列中有其他节点则将当前尾节点的 nextWaiter 指向当前节点将当前节点置为尾节点。简单总结一下这段代码的作用就是通过尾插入的方式将当前线程封装的 Node 插入到等待队列中同时可以看出Condtion 的等待队列是一个不带头节点的链式队列不带头节点是指在链表数据结构中链表的第一个节点就是实际存储的第一个数据元素而不是一个特定的头节点该节点不包含实际的数据。1不带头节点的链表链表的第一个节点就是第一个实际的数据节点。当链表为空时头引用通常称为 head指向 null。2带头节点的链表链表有一个特殊的节点作为链表的开头这个特殊的节点称为头节点。头节点通常不存储任何实际数据或者它的数据字段不被使用。无论链表是否为空头节点总是存在的。当链表为空时头节点的下一个节点指向 null。使用头节点可以简化某些链表操作因为不必特殊处理第一个元素的插入和删除。1不带头节点的链表public class Node { public int data; public Node next; public Node(int data) { this.data data; this.next null; } } public class LinkedListWithoutHead { public Node head; public void insert(int value) { Node newNode new Node(value); if (head null) { head newNode; } else { Node temp head; while (temp.next ! null) { temp temp.next; } temp.next newNode; } } }2带头节点的链表public class NodeWithHead { public int data; public NodeWithHead next; public NodeWithHead(int data) { this.data data; this.next null; } } public class LinkedListWithHead { private NodeWithHead head; public LinkedListWithHead() { head new NodeWithHead(-1); // 初始化头节点 } public void insert(int value) { NodeWithHead newNode new NodeWithHead(value); NodeWithHead temp head; while (temp.next ! null) { temp temp.next; } temp.next newNode; } }释放锁的过程将当前节点插入到等待对列之后会使当前线程释放 lock由 fullyRelease 方法实现源码如下final int fullyRelease(Node node) { //释放锁失败为true释放锁成功为false boolean failed true; try { //获取当前锁的state int savedState getState(); //释放锁成功的话 if (release(savedState)) { failed false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) //释放锁失败的话将节点状态置为取消 node.waitStatus Node.CANCELLED; } }这段代码也很容易理解调用 AQS 的模板方法 release 释放 AQS 的同步状态并且唤醒在同步队列中头节点的后继节点引用的线程如果释放成功则正常返回若失败的话就抛出异常。怎么从await方法中退出现在回过头再来看 await 方法其中有这样一段逻辑while (!isOnSyncQueue(node)) { // 3. 当前线程进入到等待状态 LockSupport.park(this); if ((interruptMode checkInterruptWhileWaiting(node)) ! 0) break; }isOnSyncQueue 方法用于判断当前线程所在的 Node 是否在同步队列中。如果当前节点的 waitStatus-2说明它在等待队列中返回 false如果当前节点有前驱节点则证明它在 AQS 队列中但是前驱节点为空说明它是头节点而头节点是不参与锁竞争的也返回 false。如果当前节点既不在等待队列中又不是 AQS 中的头节点且存在 next 节点说明它存在于 AQS 中直接返回 true。看一下同步队列与等待队列的关系图当线程第一次调用 condition.await 方法时会进入到这个 while 循环然后通过LockSupport.park(this)使当前线程进入等待状态那么要想退出 await第一个前提条件就是要先退出这个 while 循环出口就只两个地方走到 break 退出 while 循环while 循环中的逻辑判断为 false。出现第 1 种情况的条件是当前等待的线程被中断后代码会走到 break 退出第 2 种情况是当前节点被移动到了同步队列中即另外一个线程调用了 condition 的 signal 或者 signalAll 方法while 中逻辑判断为 false 后结束 while 循环。总结一下退出 await 方法的前提条件是当前线程被中断或者调用 condition.signal 或者 condition.signalAll 使当前节点移动到同步队列后。当退出 while 循环后会调用acquireQueued(node, savedState)该方法的作用是在自旋过程中线程不断尝试获取同步状态直到成功线程获取到 lock。这样也说明了退出 await 方法必须是已经获得了 condition 引用关联的 lock。await 方法示意图如下如图调用 condition.await 方法的线程必须是已经获得了 lock 的线程也就是当前线程是同步队列中的头节点。调用该方法后会使得当前线程所封装的 Node 尾插入到等待队列中。超时机制的支持condition 还额外支持超时机制使用者可调用 awaitNanos、awaitUtil 这两个方法实现原理基本上与 AQS 中的 tryAcquire 方法如出一辙。不响应中断的支持要想不响应中断可以调用condition.awaitUninterruptibly()方法该方法的源码如下public final void awaitUninterruptibly() { Node node addConditionWaiter(); int savedState fullyRelease(node); boolean interrupted false; while (!isOnSyncQueue(node)) { LockSupport.park(this); if (Thread.interrupted()) interrupted true; } if (acquireQueued(node, savedState) || interrupted) selfInterrupt(); }这段方法与上面的 await 方法基本一致只不过减少了对中断的处理。

相关新闻

深入浅出Java并发读写锁ReentrantReadWriteLock(读锁)

深入浅出Java并发读写锁ReentrantReadWriteLock(读锁)

读锁详解读锁的获取看完了写锁,再来看看读锁,读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取,也就是一种共享式锁。按照之前对 AQS 的介绍,实现共享式同步组件的同步语义需要通过重写 AQS 的 tryAcquireShared …

2026/7/3 15:33:53 阅读更多 →
【2026年版|必收藏】Agent全面爆发!万字长文详解上下文工程(小白/程序员入门必备)

【2026年版|必收藏】Agent全面爆发!万字长文详解上下文工程(小白/程序员入门必备)

1、Agent 全面爆发的前夜:上下文正在成为核心变量(2026年实践更新) 1.1、从 Chatbot 到 Agent:2026年,能力形态的本质跃迁** 在大语言模型(LLM)规模化落地之前,Chatbot 是最主流的…

2026/7/5 18:16:31 阅读更多 →
携手NVIDIA,英飞凌为人形机器人提供精准运动与高效解决方案

携手NVIDIA,英飞凌为人形机器人提供精准运动与高效解决方案

电机驱动芯片 全球功率系统和物联网领域的半导体领导者英飞凌科技股份公司(FSE代码:IFX/OTCQX代码:IFNNY)宣布携手NVIDIATechnology(简称:NVIDIA英伟达,下同)加速人形机器人领域的研…

2026/7/4 8:32:52 阅读更多 →

最新新闻

深入理解Go语言内存模型与优化

深入理解Go语言内存模型与优化

深入理解Go语言内存模型与优化Go语言以其简洁的语法、强大的并发模型和出色的性能,在现代软件开发中占据了重要地位。然而,要真正释放Go程序的潜力,开发者必须深入理解其内存模型,并掌握相关的优化技巧。Go的内存管理虽然由垃圾回…

2026/7/6 1:05:31 阅读更多 →
松下伺服电子齿轮比计算:从脉冲当量到参数设置的 3 个实战案例

松下伺服电子齿轮比计算:从脉冲当量到参数设置的 3 个实战案例

松下伺服电子齿轮比实战指南:从脉冲当量到参数设置的深度解析在工业自动化领域,伺服系统的精度控制一直是工程师们关注的核心问题。作为松下伺服系统的关键参数之一,电子齿轮比的正确设置直接关系到设备的运动精度和响应速度。本文将从一个全…

2026/7/6 1:05:31 阅读更多 →
V4L2 零拷贝与内存分配机制

V4L2 零拷贝与内存分配机制

在 Linux 嵌入式多媒体与 AI 边缘计算(如 RK3588 平台)中,为了实现极低延迟和降低 CPU 占用,通常需要打通摄像头(Camera)、图像格式转换模块(RGA/GPU)、AI 加速器(NPU&am…

2026/7/6 1:01:30 阅读更多 →
KYC形同虚设?揭秘黑产绕过金融机构身份核验全套手法

KYC形同虚设?揭秘黑产绕过金融机构身份核验全套手法

KYC(Know Your Customer,了解你的客户)并非信贷行业的专属课题,而是数字经济时代每一个需要建立"信任关系"的商业场景所共有的核心命题。无论是金融、电商、出行还是短视频,当平台试图确认"站在对面的究…

2026/7/6 1:01:30 阅读更多 →
Agentic Testing实战:自主AI测试代理架构与实现

Agentic Testing实战:自主AI测试代理架构与实现

# Agentic Testing实战:自主AI测试代理架构与实现## 一、背景与挑战:传统测试自动化的天花板当CI/CD流水线每天触发数百次测试执行,当微服务架构的API变更频率以分钟计,传统基于录制回放或关键字驱动的测试框架逐渐暴露出结构性缺…

2026/7/6 1:01:30 阅读更多 →
Windows上的安卓应用安装神器:APK安装器完整指南

Windows上的安卓应用安装神器:APK安装器完整指南

Windows上的安卓应用安装神器:APK安装器完整指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 想在Windows电脑上轻松安装安卓应用吗?APK安装…

2026/7/6 0:59:29 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻