aqs是“排队+状态管理”模板,不直接实例化;state由子类解释同步语义,如reentrantlock表重入次数、semaphore表许可数、countdownlatch表倒计时;子类须用cas操作state;clh队列是双向链表变种,head为已获资源节点;核心钩子为tryacquire/tryrelease等;公平性由子类实现决定。

AQS 不是具体锁,而是一套“排队+状态管理”的模板代码——你几乎不会直接 new 它,但 ReentrantLock、CountDownLatch、Semaphore 全都靠它撑腰。
state 变量到底管什么
所有同步逻辑都绕不开一个 volatile int state 字段:它不是固定代表“锁”,而是由子类解释的“同步状态”。比如:
-
ReentrantLock里,state == 0表示没被占用,state > 0表示重入次数 -
Semaphore(3)初始化时设state = 3,每次acquire()就 CAS 减 1,减到 0 后线程进队列 -
CountDownLatch(5)的countDown()是 CAS 减 1,减到 0 才唤醒全部等待线程
关键点:子类必须用 compareAndSetState() 或 getState()/setState() 操作它,不能直接赋值——否则破坏可见性与原子性。
CLH 队列不是真的 CLH,但思路一致
AQS 用的是 CLH(Craig, Landin, and Hagersten)锁队列的变种:每个线程失败后封装成一个 Node,插入双向链表尾部。注意几个实操细节:
- 头节点(
head)永远是“已获取资源”的线程(或哨兵),不参与竞争;真正等待的是从head.next开始的节点 - 线程阻塞前会把前驱节点的
waitStatus设为SIGNAL,意思是“我挂起了,轮到你时记得唤醒我” - 如果前驱节点取消(
CANCELLED),当前节点会往前跳过所有取消节点,重新链接——这就是为什么你常看到源码里有循环找“有效前驱”的逻辑
别被“双向链表”误导:它只用于高效取消和唤醒传播,不用于遍历全部等待者——AQS 从不遍历整个队列做操作。
tryAcquire / tryRelease 是子类唯一要写的钩子
AQS 把“排队”“挂起”“唤醒”全包了,子类只需实现这几个方法:
-
tryAcquire(int arg):决定当前线程能否抢到资源(返回 true 就成功,false 就入队) -
tryRelease(int arg):决定是否彻底释放资源(比如ReentrantLock要等state减到 0 才返回 true) - 共享模式下还要写
tryAcquireShared(int arg)和tryReleaseShared(int arg)
常见坑:tryAcquire 里不能调用 AQS 自己的同步方法(如 acquire()),会死循环;也不能在其中阻塞线程——那是 AQS 自己的事。
公平性不是 AQS 决定的,是子类策略
AQS 本身不区分公平/非公平,区别只在 tryAcquire 的实现逻辑:
- 非公平锁(默认):上来先 CAS 抢一次,抢不到再进队——可能插队
- 公平锁:先查队列有没有人等着(
hasQueuedPredecessors()),有就直接进队,不抢
所以 ReentrantLock fair = new ReentrantLock(true) 的“公平”,本质就是用了另一个 tryAcquire 实现。别指望改 AQS 源码能切公平性——得换子类逻辑。
真正难啃的地方不在队列结构或 state 操作,而在于:不同同步工具对“资源语义”的定义完全不同,但共用同一套排队骨架。看懂一个 ReentrantLock 的 AQS 流程,不代表能顺推 Phaser——后者连 state 都拆成了多字段位运算。理解 AQS,重点不是背流程,是盯住子类怎么解释 state、怎么定义“成功获取”。










