AQS 与 CLH 队列的关系、state 核心作用及内部无锁实现
AQS 与 CLH 队列的关系、state 核心作用及内部无锁实现
AQS 内部基于 CLH 单向链表队列做了 JVM 适配性优化,得到了双向链表结构的同步队列(并非全新设计);state 是 AQS 控制加锁 / 释放锁的核心状态变量;AQS 内部实现完全无锁,不依赖任何额外锁,核心靠 CAS 原子操作 + volatile 可见性保证 实现并发安全,无需手动加锁。
AQS 抽象队列同步器框架,通过volatile 修饰的共享状态变量 state控制锁的核心状态(加锁 / 释放锁的本质是 CAS 原子更新 state),通过CLH 优化版的 FIFO 双向同步队列管理锁获取失败的等待线程,基于CAS+volatile实现全流程无锁并发安全,并通过是否允许新线程插队抢锁的策略,在队列固有 FIFO 特性的基础上,灵活实现 ** 公平锁(严格 FIFO,不插队)和非公平锁(允许插队,抢不到再 FIFO 排队)** 的并发控制。
一、AQS 双向链表队列 & 原始 CLH 单向链表队列:继承核心,优化适配
AQS 的同步队列并非抛弃 CLH 队列重新设计,而是对原始 CLH 单向链表队列的深度优化与变体实现,核心设计思想完全继承,仅针对 JVM 并发场景做了适配性改造,二者是「原型 - 优化版」的关系,具体如下:
1. 完全继承的 CLH 核心设计(不变的灵魂)
AQS 同步队列保留了原始 CLH 队列的 3 个核心设计,这是实现公平同步、高效等待的基础:
- 公平排队:等待锁的线程按入队顺序获取锁,先入队先执行,避免饥饿;
- 前驱检测:线程等待时,仅自旋检测前驱节点的状态(延续 CLH「远程自旋」核心),而非自身状态;
- CAS 维护队列:队列的头尾节点更新、节点入队等操作,均通过 CAS 原子操作完成,无锁竞争开销。
2. 针对 JVM 的关键优化(单向→双向的核心原因)
原始 CLH 是为「共享内存多核场景」设计的轻量级单向链表,仅含「前驱引用 + locked 状态位」,但在 JVM 中存在适配性问题(如节点状态传播慢、不支持阻塞唤醒),因此 AQS 做了 3 个核心改造,同时将单向链表改为双向链表:
- 增加「后继节点引用(next)」:原始 CLH 仅有
prev前驱引用,AQS 给每个节点新增next后继引用,形成双向链表 —— 解决单向链表中「节点出队后,前驱节点无法快速通知后继节点」的问题,提升状态传播效率,也方便遍历队列(如取消节点、唤醒后继); - 扩展节点状态:将原始 CLH 简单的
locked布尔状态(仅「持有 / 释放」),升级为 **waitStatus整型等待状态 **(包含CANCELLED取消、SIGNAL待唤醒、CONDITION条件等待等 8 种状态)—— 支持更复杂的并发场景(如条件队列、超时等待、可中断锁); - 支持阻塞 / 唤醒:原始 CLH 仅靠自旋等待,AQS 结合
LockSupport.park()/unpark()实现「自旋→阻塞」的自适应切换 —— 当锁竞争激烈、等待时间较长时,线程从自旋转为内核态阻塞,避免 CPU 空耗,适配 JVM 中各种竞争强度的场景。
简单说:AQS 双向链表队列 = 原始 CLH 单向队列核心思想 + JVM 场景适配优化,双向结构是优化的「副产品」,核心仍是 CLH 的同步逻辑。
二、state 变量:AQS 控制加锁 / 释放锁的「核心开关」
AQS 内部的volatile int state(共享 volatile 状态变量)是整个同步机制的核心,加锁、释放锁的本质,就是对 state 变量的原子更新与状态判断,它与 CLH 双向队列是「核心状态控锁 + 等待线程排队」的协同关系,具体作用:
1. state 的核心语义(可自定义,默认适配独占锁)
state 是一个无语义的整型变量,AQS 仅提供原子操作方法,具体语义由子类(如ReentrantLock、CountDownLatch)自定义,最常见的是「独占锁语义」:
state = 0:锁处于空闲状态,当前无线程持有锁,新线程可尝试获取;state > 0:锁处于被持有状态,若为ReentrantLock(可重入锁),state的值等于「当前线程重入锁的次数」(如重入 2 次则state=2);- 释放锁时,线程需原子性减少 state 值,直到
state=0,表示锁完全释放。
2. state 与 CLH 队列的协同工作(无状态则无队列,队列是兜底)
state 是控锁的「第一优先级」,CLH 队列仅作为「锁获取失败时的等待兜底」,二者配合完成同步,核心流程:
- 线程尝试获取锁:首先通过CAS 原子操作尝试将
state从 0 改为 1(独占锁),若成功,直接获取锁,无需进入 CLH 队列; - 锁获取失败:若 CAS 更新 state 失败(锁已被持有),则创建节点并通过 CAS 入队 CLH 双向队列,进入等待状态(自旋 / 阻塞);
- 锁释放触发唤醒:持有锁的线程释放锁时,原子性将
state置为 0,随后唤醒 CLH 队列中头节点的后继节点,让其重新尝试 CAS 更新 state 获取锁。
简单说:state 决定锁的核心状态,CLH 队列管理等待锁的线程,二者协同实现「无竞争直接获取,有竞争排队等待」。
三、AQS 内部实现:完全无锁,核心依赖「CAS + volatile」
核心结论
AQS 内部不使用任何额外的锁(既不使用synchronized,也不使用自身实现的ReentrantLock等 AQS 锁),如果内部实现需要加锁,会陷入「锁的嵌套依赖」,导致额外的竞争开销,违背其「作为并发工具底层基石」的设计初衷。
无锁实现的两大核心技术(CAS 保证原子性,volatile 保证可见性)
AQS 依靠「CAS 原子操作」和「volatile 内存可见性」的配合,实现了无锁的并发安全,这是所有操作的基础,二者缺一不可:
- volatile 关键字:
- 修饰
state变量:保证state的更新对所有线程立即可见,一个线程修改 state 后,其他线程能快速感知到锁状态的变化; - 修饰 CLH 队列的节点属性(如
waitStatus、prev、next):保证队列节点的状态、引用更新在多线程间可见,避免出现「线程感知不到节点状态变化,一直无效等待」的问题。
- 修饰
- CAS 原子操作:
- 用于原子更新 state:如
compareAndSetState(0, 1),保证多线程同时尝试获取锁时,只有一个线程能成功更新 state,实现「独占获取」; - 用于原子维护 CLH 队列:如队列的尾节点更新(
compareAndSetTail(oldTail, newNode))、头节点更新(compareAndSetHead(oldHead, newNode)),保证并发下节点入队 / 出队的原子性,避免链表结构被破坏; - AQS 底层通过
Unsafe类调用 JVM 提供的 CAS 原生方法(如compareAndSwapInt、compareAndSwapObject),保证操作的底层原子性。
- 用于原子更新 state:如
AQS 内部无锁操作的典型场景
所有核心操作均通过「CAS+volatile」实现,无任何锁参与,比如:
- 获取锁:
CAS(state, 0, 1); - 节点入队:
CAS(tail, 原尾节点, 新节点); - 释放锁:
CAS(state, 重入次数, 重入次数-1); - 队列头节点更新:
CAS(head, 原头节点, 新头节点)。
核心总结
- 队列关系:AQS 双向链表队列是原始 CLH 单向链表队列的 JVM 优化版,继承公平排队、前驱检测、CAS 维护的核心思想,新增后继引用、扩展状态、支持阻塞唤醒,适配 Java 并发场景;
- state 作用:
volatile int state是 AQS控锁核心,加锁 / 释放锁的本质是 CAS 原子更新 state,队列仅为锁获取失败的线程提供排队等待机制; - 内部实现:AQS 完全无锁,不依赖任何额外锁,核心靠「CAS 保证原子操作(state 更新、队列维护)+ volatile 保证内存可见性(状态、节点引用)」实现并发安全,这是其作为 JUC 底层基石的高性能关键。
更多推荐




所有评论(0)