0

0

Java并发编程中如何利用CLH队列实现公平锁_AQS底层逻辑解析

P粉602998670

P粉602998670

发布时间:2026-02-26 15:33:58

|

229人浏览过

|

来源于php中文网

原创

java并发编程中如何利用clh队列实现公平锁_aqs底层逻辑解析

CLH队列不是链表,是逻辑上的自旋等待队列

很多人一看到“CLH”就默认是双向链表结构,直接去翻 AbstractQueuedSynchronizer 里的 Node 字段,结果发现 prevnext 并不用于构建真实链表——它们只在取消或超时时做清理用。CLH 的核心是每个线程持有一个本地的 Node,靠 pred 指针指向前驱节点的 status 字段来判断是否该轮到自己获取锁。

  • 真正构成“队列”的是线程间对前驱 status 的 volatile 读写,不是指针遍历
  • Node 初始化时 status = 0,入队后被设为 Node.SIGNAL(-1),表示“唤醒后继”
  • 前驱节点释放锁时,会把自身 status 设为 0,并调用 LockSupport.unpark() 唤醒后继——但这个唤醒动作不一定立即生效,后继仍需自旋检查前驱状态

为什么 AQS 不直接用 CLH 原始版本而要改造?

原始 CLH 锁在 NUMA 架构下有缓存行伪共享问题,且无法支持取消操作。AQS 改造成“变种 CLH”:把前驱节点的 status 当作信号位,同时允许节点在中断或超时时主动出队。

  • 原始 CLH 要求所有线程严格按入队顺序退出,AQS 必须支持 Thread.interrupt()tryAcquireNanos() 中断,所以引入 CANCELLED(1)状态并做惰性清理
  • shouldParkAfterFailedAcquire() 是关键函数:它不断跳过已取消的前驱,直到找到一个有效 SIGNAL 节点才返回 true,否则返回 false 触发重试
  • 如果前驱是 CANCELLED,当前节点会尝试用 CAS 把前驱的 next 指向自己,实现“剪枝”,但这步不保证成功,所以清理是渐进式的

公平锁里 acquire() 和 tryAcquire() 的调用时机差异

公平锁的 lock() 方法最终走的是 acquire(1),但它在真正排队前会先调用一次 tryAcquire(1) —— 这次调用会检查队列是否为空、且当前线程是不是队首,而不是简单看 state 是否为 0。

Warp
Warp

新一代的终端工具(内置AI命令搜索)

下载
  • 非公平锁的 tryAcquire() 只检查 state == 0 就尝试 CAS 设置;公平锁则额外加了 !hasQueuedPredecessors() 判断
  • hasQueuedPredecessors() 的实现很精巧:它不遍历队列,而是检查 head.next != currentNodehead.next != null,本质是判断当前线程是否是队列中第一个等待者
  • 这个判断必须放在 CAS 之前,否则可能刚检查完队列为空,另一线程就插入头部,导致插队——所以公平锁的“公平性”其实是在入队那一刻确定的,不是在唤醒时

调试时怎么看 CLH 队列实际状态?

别依赖 IDE 的变量视图看 AQS 内部字段,headtail 是 volatile 的,且节点随时可能被取消或唤醒,静态快照意义不大。真正有用的是 getQueueLength()hasQueuedThreads() 这类诊断方法,或者打日志时记录 Thread.currentThread().getName() + node.waitStatus

立即学习Java免费学习笔记(深入)”;

  • node.waitStatus 的常见值:0(初始)、-1(SIGNAL)、1(CANCELLED)、-2(CONDITION)、-3(PROPAGATE)
  • 注意 waitStatus == 0 不代表节点无效,可能是刚入队还没被前驱标记 SIGNAL,也可能是前驱刚释放锁正在唤醒它
  • 用 jstack 查线程堆栈时,阻塞在 Unsafe.park() 的线程大概率在 CLH 队列中,但不能反推:有些线程 park 是因为条件队列(ConditionObject),不是 AQS 同步队列

CLH 的难点不在结构本身,而在状态流转的时机和内存可见性保障——比如前驱改 status 和后继读 status 之间,靠的是 volatile 语义和 JVM 的 happens-before 规则,不是锁或 synchronized。这点一旦想错,就容易在复现竞争 bug 时绕弯路。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

248

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

906

2024.03.01

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

71

2025.10.23

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

425

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

597

2023.08.10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

425

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

597

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

721

2023.08.10

Golang 实际项目案例:从需求到上线
Golang 实际项目案例:从需求到上线

《Golang 实际项目案例:从需求到上线》以真实业务场景为主线,完整覆盖需求分析、架构设计、模块拆分、编码实现、性能优化与部署上线全过程,强调工程规范与实践决策,帮助开发者打通从技术实现到系统交付的关键路径,提升独立完成 Go 项目的综合能力。

1

2026.02.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3.9万人学习

C# 教程
C# 教程

共94课时 | 10.2万人学习

Java 教程
Java 教程

共578课时 | 72.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号