AQS双向队列状态迁移源码剖析
AQS双向队列状态迁移源码剖析前言AQS双向队列状态迁移源码剖析一、 AQS 双队列架构概述二、 核心流程从 await() 到 signal() 的节点转移三、 OpenJDK8源码详细逐行剖析1. 节点的产生与入队Condition Queue2. 彻底释放锁AQS State 归零3. 唤醒与节点转移的桥梁signal()4. 转移的核心实现transferForSignal(Node)5. 同步队列的尾插法enq(Node)6. 自旋检测isOnSyncQueue(Node)四、 核心设计思想与工程细节1. **为什么在 transferForSignal 中改变状态要用 CAS**2. **前驱节点状态转化为 Node.SIGNAL 的深意**3. **无锁化Lock-Free的双向队列尾插安全**前言本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限文中内容难免存在疏漏恳请读者不吝指正。AQS双向队列状态迁移源码剖析在Java并发包java.util.concurrent中AbstractQueuedSynchronizerAQS是构建锁和其他同步组件的核心基石。作为软件开发工程师深入理解其双向队列Sync Queue即同步队列/CLH队列与单向条件队列Condition Queue之间的节点转移机制是掌握高并发底层原理的必经之路。下面我们将结合OpenJDK8的具体源码深度剖析当线程调用Condition.await()和Condition.signal()时AQS 内部节点Node是如何在两个队列之间进行流转与状态切换的。一、 AQS 双队列架构概述AQS 内部维护了两种互补的队列结构同步队列Sync Queue / CLH Queue结构双向链表。通过head和tail指针维护。节点关系节点间通过prev和next指针双向链接。作用管理等待获取锁的线程。条件队列Condition Queue结构单向链表。由 AQS 的内部类ConditionObject维护通过firstWaiter和lastWaiter指针管理。节点关系节点间仅通过nextWaiter指针单向链接。作用管理因未满足特定条件而调用await()进入等待状态的线程。这两个队列使用的都是 AQS 的静态内部类Node但节点的关键属性和状态waitStatus在不同队列中承载着不同的含义waitStatus Node.CONDITION (-2)代表节点存在于条件队列中。waitStatus Node.SIGNAL (-1)代表该节点的后继节点需要被唤醒存在于同步队列中。waitStatus 0初始化状态。二、 核心流程从await()到signal()的节点转移当一个持有锁的线程调用condition.await()时它会释放锁并进入条件队列挂起当另一个线程调用condition.signal()时处于条件队列首部的节点将被转移到同步队列中等待重新获取锁。这一过程的底层转移逻辑由以下几个核心步骤组成[持有锁的线程] - 调用 await() - 释放锁 | v (封装为 Node.CONDITION) [Condition Queue (单向)] - 线程挂起 (LockSupport.park) | | (另一个线程调用 signal()) v [transferForSignal] - CAS修改状态为0 - 移出 Condition Queue | v (调用 enq() 尾插) [Sync Queue (双向)] - 前驱节点状态设为 SIGNAL - 等待/唤醒三、 OpenJDK8源码详细逐行剖析1. 节点的产生与入队Condition Queue当线程调用await()时首先会调用addConditionWaiter()将当前线程封装为Node并放入条件队列尾部。// java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObjectpublicfinalvoidawait()throwsInterruptedException{if(Thread.interrupted())thrownewInterruptedException();// 1. 将当前线程封装为 Node加入到 Condition 队列尾部NodenodeaddConditionWaiter();// 2. 释放当前线程持有的所有锁AQS state并返回释放前的 state以便后续重入intsavedStatefullyRelease(node);intinterruptMode0;// 3. 自旋判断如果当前节点不在同步队列中则挂起线程while(!isOnSyncQueue(node)){LockSupport.park(this);// 线程在此处阻塞挂起// 如果在挂起期间或被唤醒时发生中断则跳出循环处理中断if((interruptModecheckInterruptWhileWaiting(node))!0)break;}// 4. 此时节点已被转移到同步队列调用 acquireQueued 尝试在同步队列中阻塞获取锁if(acquireQueued(node,savedState)interruptMode!THROW_IE)interruptModeREINTERRUPT;if(node.nextWaiter!null)// 清理条件队列中被取消的节点unlinkCancelledWaiters();if(interruptMode!0)reportInterruptAfterWait(interruptMode);}/** * 将当前线程作为等待者添加到条件队列的尾部 */privateNodeaddConditionWaiter(){NodetlastWaiter;// 如果尾部节点已被取消waitStatus 不是 CONDITION则进行清理if(t!nullt.waitStatus!Node.CONDITION){unlinkCancelledWaiters();tlastWaiter;}// 创建一个 waitStatus 为 CONDITION (-2) 的新节点NodenodenewNode(Thread.currentThread(),Node.CONDITION);if(tnull)firstWaiternode;// 队列为空初始化头部elset.nextWaiternode;// 单向链表尾插lastWaiternode;// 更新尾指针returnnode;}2. 彻底释放锁AQS State 归零进入条件队列后线程必须完全释放所持有的锁否则会造成死锁。// java.util.concurrent.locks.AbstractQueuedSynchronizer/** * 释放当前节点持有的所有锁还原 state */finalintfullyRelease(Nodenode){booleanfailedtrue;try{intsavedStategetState();// 释放独占锁若释放失败如未持有锁调用 await会抛出 IllegalMonitorStateExceptionif(release(savedState)){failedfalse;returnsavedState;}else{thrownewIllegalMonitorStateException();}}finally{if(failed)// 释放失败则将节点状态置为 CANCELLED 废弃node.waitStatusNode.CANCELLED;}}3. 唤醒与节点转移的桥梁signal()当另一个线程调用signal()时AQS 开始将条件队列头部的有效节点转移至同步队列中。// java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObjectpublicfinalvoidsignal(){// 1. 验证当前线程是否持有独占锁if(!isHeldExclusively())thrownewIllegalMonitorStateException();NodefirstfirstWaiter;if(first!null)// 2. 唤醒条件队列中的第一个节点doSignal(first);}/** * 循环遍历条件队列找到第一个未取消的节点并将其转移到同步队列中 */privatevoiddoSignal(Nodefirst){do{// 将 firstWaiter 指向下一个节点断开当前节点的单向链接if((firstWaiterfirst.nextWaiter)null)lastWaiternull;first.nextWaiternull;// 帮助 GC}while(!transferForSignal(first)// 核心转移方法如果转移失败说明该节点已取消则继续尝试下一个(firstfirstWaiter)!null);}4. 转移的核心实现transferForSignal(Node)transferForSignal是整个 AQS 双向队列与条件队列交互最精妙的地方。它利用无锁 CAS 操作安全地改变节点状态并将节点拼接到同步队列尾部。// java.util.concurrent.locks.AbstractQueuedSynchronizer/** * 将节点从条件队列转移到同步队列。若成功则返回 true。 */finalbooleantransferForSignal(Nodenode){/* * 1. 尝试将节点状态由 CONDITION (-2) 通过 CAS 修改为初始状态 0。 * 如果修改失败说明该节点在被唤醒前由于超时或中断已经被取消CANCELLED。 */if(!compareAndSetWaitStatus(node,Node.CONDITION,0))returnfalse;/* * 2. 调用 enq 将该节点尾插到同步队列Sync Queue中。 * 注意enq 方法是死循环自旋的 CAS 插入并返回该节点在同步队列中的【前驱节点 p】。 */Nodepenq(node);intwsp.waitStatus;/* * 3. 核心优化 * 如果前驱节点 p 已经处于取消状态 (ws 0) * 或者尝试将前驱节点 p 的状态修改为 SIGNAL (-1) 失败说明前驱节点在此期间状态发生了改变如取消 * 那么为了安全起见我们必须立刻调用 LockSupport.unpark 唤醒当前节点的线程。 * 被唤醒的线程会在 await() 中的 acquireQueued() 流程中重新同步、自适应修正状态。 * * 如果 CAS 设置前驱为 SIGNAL 成功则当前线程可以继续保持 park 状态 * 因为前驱节点在释放锁时release 流程会负责将当前线程唤醒。 */if(ws0||!compareAndSetWaitStatus(p,ws,Node.SIGNAL))LockSupport.unpark(node.thread);returntrue;}5. 同步队列的尾插法enq(Node)enq方法负责在多线程并发竞争下安全地将节点插入到双向同步队列的尾部并建立起双向链接关系。// java.util.concurrent.locks.AbstractQueuedSynchronizer/** * 将节点插入同步队列尾部必要时进行初始化。 * return 返回插入节点的前驱节点 */privateNodeenq(finalNodenode){for(;;){// 死循环自旋确保 CAS 成功Nodettail;if(tnull){// 1. 队列为空进行初始化if(compareAndSetHead(newNode()))// CAS 创建哑节点 (Dummy Node) 作为 headtailhead;// 此时 head 和 tail 指向同一个哑节点}else{// 2. 队列不为空将新节点的前驱指针 prev 指向当前的 tailnode.prevt;// 3. CAS 尝试将 tail 指向新节点 nodeif(compareAndSetTail(t,node)){// 4. 关键点CAS 成功后将原尾节点的 next 指针指向新节点完成双向链表的闭环t.nextnode;returnt;// 返回前驱节点}}}}6. 自旋检测isOnSyncQueue(Node)在await()的挂起循环中线程通过isOnSyncQueue判断自己是否已经被成功转移到同步队列中。只有当节点存在于同步队列时它才有资格去竞争锁。// java.util.concurrent.locks.AbstractQueuedSynchronizer/** * 判断节点是否已经在同步队列Sync Queue中 */finalbooleanisOnSyncQueue(Nodenode){// 1. 如果状态还是 CONDITION或者前驱指针 prev 仍为 null则一定还在条件队列中if(node.waitStatusNode.CONDITION||node.prevnull)returnfalse;// 2. 如果 next 指针不为空说明它一定在同步队列中因为条件队列只用 nextWaiterif(node.next!null)returntrue;/* * 3. 如果 node.prev ! null 但 node.next null说明该节点可能正在执行 enq 入队。 * 在 enq() 中是先执行 node.prev t再执行 CAS tail最后才执行 t.next node。 * 此时需要从尾部向前遍历同步队列做最后的确认。 */returnfindNodeFromTail(node);}/** * 从同步队列尾部向前遍历寻找是否存在该节点 */privatebooleanfindNodeFromTail(Nodenode){Nodettail;for(;;){if(tnode)returntrue;if(tnull)returnfalse;tt.prev;}}四、 核心设计思想与工程细节作为软件开发工程师我们可以从这段优雅的代码中总结出 AQS 极具工业级水准的底层设计细节1.为什么在transferForSignal中改变状态要用 CAS在多线程环境下一个处于条件队列中挂起的线程可能在被signal()唤醒的同时遭遇了线程中断或超时。如果发生了中断线程自身会调用transferAfterCancelledWait尝试将自己移入同步队列。这就会与signal()线程产生并发冲突。AQS 通过compareAndSetWaitStatus(node, Node.CONDITION, 0)这一行 CAS 操作精妙地充当了“仲裁者”。谁 CAS 成功谁就负责将该节点放入同步队列。失败的一方则避开重复入队从而保证了线程状态的一致性。2.前驱节点状态转化为Node.SIGNAL的深意在同步队列中AQS 采用了一种被动唤醒机制一个节点在同步队列中被挂起后它不需要不断自旋而是靠它的前驱节点在释放锁时来唤醒它。因此在transferForSignal成功将节点尾插到同步队列后必须将前驱节点的状态改为Node.SIGNAL (-1)。这相当于新节点对前驱节点说“老哥我排在你后面了你待会儿释放锁的时候记得叫醒我。”如果前驱节点已经取消或者修改状态失败当前线程就会立刻被unpark唤醒防止其在同步队列中永久“失联”。3.无锁化Lock-Free的双向队列尾插安全在enq(Node node)中双向链表的构建分为三步node.prev t;线程本地安全操作CAS 改变tail指针原子分水岭t.next node;多线程可见这种顺序导致了在极短的瞬间第 2 步完成第 3 步未完成时从 head 顺着next指针往后找可能找不到新节点但从 tail 逆着prev指针往前找是一定能找到的。这也是为什么isOnSyncQueue的退化检查方法findNodeFromTail必须从后往前from tail遍历的原因。

相关新闻

AI-Based Measurement of Innovation: Mapping Expert Insight into Large Language Model Applications

AI-Based Measurement of Innovation: Mapping Expert Insight into Large Language Model Applications

文章主要内容总结 本文提出了一种名为MicroMix的混合精度量化框架,专门针对大型语言模型(LLMs)在NVIDIA Blackwell架构上的高效推理设计。该框架结合了基于微缩放(Microscaling, MX)数据格式的量化算法和矩阵乘法内核,通过灵活分配MXFP4、MXFP6、MXFP8精度通道,在保证模…

2026/7/27 23:54:29阅读更多 →
MATLAB图形标注与grid网格线优化指南

MATLAB图形标注与grid网格线优化指南

1. MATLAB图形标注基础与grid网格线概述在数据可视化领域,MATLAB作为工程计算的标准工具,其图形标注功能直接影响着科研图表的信息传达效率。grid网格线作为坐标系的骨架结构,看似简单却承担着多重使命:它既是数据点的定位参考系&…

2026/7/27 23:52:29阅读更多 →
TVA数字小脑:具身智能的物理交互革命(13)

TVA数字小脑:具身智能的物理交互革命(13)

前沿技术探索:AI智能体视觉(TVA,Transformer-based Vision Agent)是依托Transformer架构与“因式智能体”理论所构建的颠覆性工业视觉技术,是集深度强化学习(DRL)、卷积神经网络(CNN…

2026/7/27 23:52:28阅读更多 →
【Bug已解决】[Bug]: key error: ‘Layer.34.mlp.experts.gate_up_proj‘ 解决方案

【Bug已解决】[Bug]: key error: ‘Layer.34.mlp.experts.gate_up_proj‘ 解决方案

【Bug已解决】[Bug]: key error: Layer.34.mlp.experts.gate_up_proj 解决方案 一、现象长什么样 在用 vLLM 加载一个 MoE(混合专家)模型的权重时,启动阶段直接抛出一个 KeyError,程序在 load_state_dict 阶段崩溃,日志…

2026/7/28 1:02:55阅读更多 →
【Bug已解决】[Bug]: GLM5.2 RuntimeError: The expanded size of the tensor (10210) must match the existing

【Bug已解决】[Bug]: GLM5.2 RuntimeError: The expanded size of the tensor (10210) must match the existing

【Bug已解决】[Bug]: GLM5.2 RuntimeError: The expanded size of the tensor (10210) must match the existing size (16384) at non-singleton dimension 1. Target sizes: [12, 10210]. Tensor sizes: [12, 16384] 解决方案 一、现象长什么样 在 vLLM 上跑 GLM-5.2&#x…

2026/7/28 1:02:55阅读更多 →
从 0 到 1 构建 AI 需求分析引擎:Skills 技术架构与落地全复盘

从 0 到 1 构建 AI 需求分析引擎:Skills 技术架构与落地全复盘

从 0 到 1 构建 AI 需求分析引擎:Skills 技术架构与落地全复盘 需求分析不是把一段自然语言丢给大模型,然后等待一份“看起来像 PRD”的回答。 真正可落地的方案,必须把需求理解、知识约束、结构化编译、冲突校验、接口推导和工程治理串成一条可验证的生产链路。 很多团队做…

2026/7/28 1:02:55阅读更多 →
【Springboot毕设全套源码+文档】基于springboot顺丰仓储管理信息系统的设计与实现(丰富项目+远程调试+讲解+定制)

【Springboot毕设全套源码+文档】基于springboot顺丰仓储管理信息系统的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/28 1:02:55阅读更多 →
HarmonyOS应用实战-启示散页-48-HAR 公共模型别引 UI 依赖:让 Deck、Answer 和 Result 保持纯净

HarmonyOS应用实战-启示散页-48-HAR 公共模型别引 UI 依赖:让 Deck、Answer 和 Result 保持纯净

HarmonyOS应用实战-启示散页-48-HAR 公共模型别引 UI 依赖:让 Deck、Answer 和 Result 保持纯净 公共模型一开始只是 Deck、Answer、Favorite。页面需要一个展示字段时,开发者很容易顺手把 State、NavPathStack 或页面类型塞进模型。这样数据层开始依赖 …

2026/7/28 1:02:55阅读更多 →
[Android] TV 迅雷云盘 -TV纯净版+支持4K、HDR播放等

[Android] TV 迅雷云盘 -TV纯净版+支持4K、HDR播放等

[Android] TV 迅雷云盘 -TV纯净版支持4K、HDR播放等 链接:https://pan.xunlei.com/s/VOyYC93W4rzN75x_oB7j5W6tA1?pwdsk32# 一款可以在电视上使用的云盘工具app。集成了迅雷强大的云盘服务,可以将用户的文件和媒体内容同步至电视端,提供无…

2026/7/28 1:00:55阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/27 1:14:34阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/27 1:14:52阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/27 1:14:56阅读更多 →
告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:29阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑:把几百页的财报、法规、技术手册扔给向量库,问一个具体问题,搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了,要么藏在几十条结果的最下面。语义相似≠真正相关,这个…

2026/7/28 0:00:29阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营,从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候,每天刷半小时抖音,手动把爆款视频的口播敲进备忘录,一条2分钟的视频得花十来分钟,碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:29阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/27 16:57:54阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/26 19:05:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/26 19:05:21阅读更多 →