CLH队列机制的工作原理
CLH队列(Craig-Landin-Hagersten队列)是一种基于链表的自旋锁等待队列,是Java并发包中AQS(AbstractQueuedSynchronizer)框架的核心实现机制。
核心原理
CLH队列是一种**FIFO(先进先出)**的双向链表结构,每个节点代表一个等待锁的线程。其核心思想是:每个线程只监视前一个线程的状态,而不是竞争共享变量。具体工作流程如下:
-
节点结构:每个节点包含以下关键属性
- waitStatus:节点等待状态(如CANCELLED=1、SIGNAL=-1、CONDITION=-2等)
- prev:前驱节点引用
- next:后继节点引用
- thread:等待的线程
- nextWaiter:条件队列中的后继节点
-
入队机制:
- 当线程尝试获取锁失败时,会将当前线程构造成一个Node节点
- 通过CAS操作原子性地将节点添加到队列尾部(尾插法)
- 随后将前驱节点状态设置为SIGNAL(-1),表示"后继节点需要被唤醒"
-
自旋等待:
- 线程在获取锁失败后,会自旋检查前驱节点的waitStatus状态
- 如果前驱节点状态为SIGNAL(-1),表示前驱节点正在等待释放锁,当前线程继续自旋等待
- 一旦前驱节点释放锁(状态变为CANCELLED),当前线程即可获取锁
-
出队机制:
- 当线程释放锁时,会唤醒队列中第一个等待的线程(即head的后继节点)
- 被唤醒的线程会尝试获取锁,成功后将自己设置为新的head节点
- 旧head节点被废弃,等待垃圾回收
AQS中的CLH队列实现特点
-
虚拟头节点:AQS使用一个虚拟的head节点(不存储实际线程),简化队列操作
-
状态管理:
- SIGNAL(-1):前驱节点已释放锁,当前节点需要被唤醒
- CANCELLED(1):线程已取消等待(超时或中断)
- CONDITION(-2):线程在Condition队列中等待
- PROPAGATE(-3):共享模式下可传播的状态
-
性能优化:
- AQS对CLH队列进行了变种实现,避免所有线程都自旋
- 在插入尾节点时,会将前置节点设置为SIGNAL状态并通过LockSupport.park()阻塞当前线程
- 通过这种方式避免了所有线程一直自旋导致的CPU 100%占用问题
优势与特点
- 公平性:保证线程按照先来先服务的顺序获取锁,避免线程饥饿
- 高效性:每个线程只需关注前驱节点状态,降低线程间通信开销
- 可扩展性:通过动态调整队列长度,适应不同并发场景
- 内存效率:相比MCS锁,CLH只需维护tail指针的原子更新,减少50%的CAS操作
与MCS锁的对比
| 特性 | CLH队列 | MCS锁 |
|---|---|---|
| 队列结构 | 单向链表(看前驱) | 单向链表(看自己) |
| 自旋位置 | 前驱节点 | 自己的节点 |
| 空间重用 | 是(通过前驱复用) | 否(每次新建) |
| 实现复杂度 | 较低 | 较高 |
| 缓存局部性 | 高(本地自旋) | 一般 |
CLH队列作为Java并发框架的核心机制,通过简单的链表结构和自旋等待机制,实现了高效的线程同步和协作,是ReentrantLock、Semaphore等同步组件底层实现的基础。
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 软件从业者Hort
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

