基于:JDK 17(java.util.concurrent.locks.AbstractQueuedSynchronizer,JDK 1.5 引入,8~17 核心逻辑基本稳定)
AQS(AbstractQueuedSynchronizer,抽象队列同步器)是整个 JUC 并发包的"地基"------ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock、ThreadPoolExecutor.Worker 全部建立在它之上。本文从设计思想出发,剖析它的同步状态、CLH 变体等待队列、模板方法与 Condition 机制,用主线流程 + 可运行代码把"AQS 到底在做什么"讲透。
目录
- 核心理论
- [1.1 一句话定位](#1.1 一句话定位)
- [1.2 它解决什么问题](#1.2 它解决什么问题)
- [1.3 基于 AQS 的并发工具全景](#1.3 基于 AQS 的并发工具全景)
- [1.4 三大核心抽象](#1.4 三大核心抽象)
- 设计思想与核心机制
- [2.1 总体设计思路](#2.1 总体设计思路)
- [2.2 模板方法模式](#2.2 模板方法模式)
- [2.3 同步状态 state](#2.3 同步状态 state)
- [2.4 等待队列 CLH 变体](#2.4 等待队列 CLH 变体)
- [2.5 节点 Node 与 waitStatus](#2.5 节点 Node 与 waitStatus)
- [2.6 排队与出队机制](#2.6 排队与出队机制)
- [2.7 阻塞与唤醒 LockSupport](#2.7 阻塞与唤醒 LockSupport)
- [2.8 公平锁与非公平锁](#2.8 公平锁与非公平锁)
- [2.9 中断与超时支持](#2.9 中断与超时支持)
- [2.10 条件队列 Condition](#2.10 条件队列 Condition)
- 主线流程剖析
- [3.1 独占获取加锁主线](#3.1 独占获取加锁主线)
- [3.2 独占释放唤醒主线](#3.2 独占释放唤醒主线)
- [3.3 共享模式获取与传播](#3.3 共享模式获取与传播)
- [3.4 Condition 完整链路](#3.4 Condition 完整链路)
- [3.5 一次完整的锁竞争时序](#3.5 一次完整的锁竞争时序)
- 类关系图
- [4.1 JUC 继承全景](#4.1 JUC 继承全景)
- [4.2 AQS 内部结构](#4.2 AQS 内部结构)
- [4.3 同步队列与条件队列形态](#4.3 同步队列与条件队列形态)
- 动手实现一个同步器
- [5.1 基于 AQS 的自定义互斥锁](#5.1 基于 AQS 的自定义互斥锁)
- [5.2 升级为可重入与公平](#5.2 升级为可重入与公平)
- [5.3 基于 AQS 实现共享闸门 MyLatch](#5.3 基于 AQS 实现共享闸门 MyLatch)
- [5.4 对照与验证 Demo](#5.4 对照与验证 Demo)
- [扩展点与常见问题 FAQ](#扩展点与常见问题 FAQ)
- [6.1 高频面试问题速答](#6.1 高频面试问题速答)
- [6.2 源码细节点辨析](#6.2 源码细节点辨析)
- [6.3 扩展实践方向与总结](#6.3 扩展实践方向与总结)
1. 核心理论 ★★★
1.1 一句话定位
关键认知 :AQS = 一个 volatile int 状态(state)+ 一个 CLH 变体线程等待队列 + 一套模板方法协议 。
所有"锁 / 信号量 / 门闩"本质上都只做一件事:尝试把 state 改成符合条件的值,改不成就排进队列等着别人改完叫醒自己。
java
// AbstractQueuedSynchronizer 字段一览(JDK 17,省略部分条件锁支持)
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
private transient volatile Node head; // 同步队列头(哨兵,thread == null)
private transient volatile Node tail; // 同步队列尾
private volatile int state; // 核心:同步状态
private transient Thread exclusiveOwnerThread; // 继承自 AbstractOwnableSynchronizer
// state 的三种受保护访问方式
protected final int getState() { return state; }
protected final void setState(int newState) { state = newState; }
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
- state 用
volatile保证可见性,用 CAS(compareAndSetState,底层 Unsafe) 保证原子修改; - 队列的 head / tail 均 volatile,入队用 CAS 尾插。
1.2 它解决什么问题
在 AQS 出现前,Java 里做线程互斥只有 synchronized:好用但不灵活------不能响应中断、不能超时、不能做公平排队、多条件等待要自己造、共享式获取(N 个许可)无从谈起。
AQS 把并发同步的"脏活"全部接管,只把一个问题留给子类:"此时你(调用方)能不能获得/释放这个资源?"
| 脏活(AQS 免费提供) | 子类只需回答 |
|---|---|
| 抢锁失败的线程排队(CLH 队列) | tryAcquire:能获得吗? |
| 队首线程阻塞与唤醒(LockSupport) | tryRelease:能释放吗? |
| 中断传播、超时取消、节点清理 | tryAcquireShared:还剩多少? |
| Condition 等待/通知协议 | tryReleaseShared:归还后够吗? |
这就是模板方法模式------变化的部分下沉给子类,不变的部分固化在父类。
1.3 基于 AQS 的并发工具全景
| 类 | AQS 模式 | state 的语义 | 抢不到怎么办 |
|---|---|---|---|
| ReentrantLock | 独占 | 持有计数(0=无锁,>0 表示重入次数) | 入队 park,解锁时唤醒 |
| ReentrantReadWriteLock | 独占+共享 | 高 16 位=读锁持有数,低 16 位=写锁重入数 | 写锁独占、读锁可并发 |
| Semaphore | 共享 | 剩余许可数(permit) | 许可为 0 时入队等待 |
| CountDownLatch | 共享 | 剩余倒计数 | 计数归 0 前 await 线程全部入队 |
| ThreadPoolExecutor.Worker | 独占(不可重入) | 0/1:worker 是否"被占用" | 核心线程数之外的抢占排队 |
⚠️ 注意 FutureTask 是反例 :JDK 5~7 的 FutureTask 基于 AQS 实现状态机;JDK 8 起为性能自研
volatile state + Unsafe CAS甩开了 AQS。这说明 AQS 也有性能边界,不是万金油。
1.4 三大核心抽象
- 一个状态(state) :int 可以表达"有/无"(锁)、"还剩几个"(信号量)、"几重"(重入计数)、甚至切分成高低 16 位表达两种信息(读写锁)。这是它比
boolean locked强大的根本原因。 - 一个等待队列(CLH 变体):抢不到 state 的线程排成队列,先进先出(按排队序获得唤醒机会,公平性由子类决定)。
- 一套协议(模板方法):独占/共享两套获取-释放钩子,配合可中断、可超时、条件等待的完整模板。
2. 设计思想与核心机制 ★★★
2.1 总体设计思路
AQS 的设计可以概括为一句:"把锁的语义做成一个可 CAS 的整数,把等锁的线程做成一条队列。"
- 获得资源成功 → 修改 state 返回(不排队);
- 获得资源失败 → 包装成 Node 塞进队尾,挂起自己(不空转);
- 有人释放资源 → 从队头挑下一个线程唤醒,被唤醒者重新尝试抢 state;
- 所有操作在无锁前提下靠 volatile + CAS 完成,不依赖任何系统级互斥。
设计目标:等待线程 O(1) 时间定位自己的邻居 (CAS 队尾入队、队头出队),并且不会饿死 (排队序),又不强制公平(把是否插队的决定权交给子类的 tryAcquire)。
2.2 模板方法模式
AQS 对子类暴露两套钩子方法(必须覆盖才能用),子类不同实现组合出不同同步器:
| 钩子方法 | 语义 | 覆盖者 |
|---|---|---|
tryAcquire(int arg) |
独占模式尝试获取,成功返回 true | ReentrantLock / ReentrantReadWriteLock 写锁 / ThreadPoolExecutor.Worker |
tryRelease(int arg) |
独占模式尝试释放 | 同上 |
tryAcquireShared(int arg) |
共享模式尝试获取,返回剩余值(>=0 表示成功) | Semaphore / CountDownLatch / 读锁 |
tryReleaseShared(int arg) |
共享模式尝试释放 | 同上 |
isHeldExclusively() |
当前线程是否独占持有(供 Condition 校验) | 独占类同步器 |
模板方法本身都是 final,子类无法改写排队逻辑:
java
// 独占获取模板:先问钩子,失败就排队等
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 排队 + 阻塞,若被中断过则补中断
selfInterrupt();
}
// 独占释放模板:钩子返回 true 才去唤醒队首后继
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒头节点的下一个有效节点
return true;
}
return false;
}
2.3 同步状态 state
state是 int 类型、volatile 修饰,三种受保护操作:getState()、setState()、compareAndSetState()。- 写者安全规则:"改 state 之前做的事" (如写入被保护的业务数据)对"之后读到新 state 的线程"可见------靠 volatile 的 happens-before。
- 每个同步器自由定义 state 的 bit 布局:
| 同步器 | state 布局 | 说明 |
|---|---|---|
| ReentrantLock | 整个 int = 重入次数 | state = 0 无锁;每次 lock +1,unlock -1,归 0 才算真正释放 |
| ReentrantReadWriteLock | `高16位 | 低16位` |
| Semaphore | 整个 int = 剩余许可 | 可负?不------许可不足时返回负剩余值给 AQS 表示"拿不到"(tryAcquireShared 返回值语义) |
| CountDownLatch | 整个 int = 剩余倒数 | 0 表示门已开 |
细节 :
tryAcquireShared的返回值是三态 ------正数(成功且还有剩余)、0(成功但已耗尽)、负数(失败需排队)。CountDownLatch 的tryAcquireShared就写成getState() == 0 ? 1 : -1。
2.4 等待队列 CLH 变体
AQS 的队列基于 CLH 锁(Craig, Landin, Hagersten 三人的自旋锁)改造而来:
| 对比项 | 原始 CLH 自旋锁 | AQS 的 CLH 变体 |
|---|---|---|
| 排队介质 | 隐式前驱(只存前驱的锁状态,单链表) | 显式双向链表(prev / next 都存) |
| 等待方式 | 自旋轮询前驱 | park 挂起,前驱释放时 unpark 唤醒(省 CPU) |
| 取消节点 | 无需处理(无独立节点) | 支持节点取消(超时/中断)后出链清理 |
| 状态传递 | 轮询前驱标志位 | 每个节点用 waitStatus 显式标记"我释放后会唤醒后继" |
为什么改成双向?
- 找后继唤醒必须 O(1) :当前线程释放锁后要精确唤醒下一个等待者(队列是"排他性唤醒"),单向链做不到;
- 取消节点会断链 :被取消的节点把自己的 next 置为自身(help GC),后继节点不能依赖 next 向后遍历,只能从 tail 沿 prev 逆序找第一个有效节点(见 3.2 unparkSuccessor 的"倒着找"逻辑)。
2.5 节点 Node 与 waitStatus
java
abstract static class Node {
static final int CANCELLED = 1; // 节点已取消(超时/中断放弃),永不再变
static final int SIGNAL = -1; // 前驱承诺:释放后要唤醒我("我排你后面,你走时叫我")
static final int CONDITION = -2; // 节点在条件队列中,等 signal
static final int PROPAGATE = -3; // 共享模式:释放动作应继续向后传播
volatile int waitStatus; // 上述状态之一或 0(新建节点)
volatile Node prev; // 同步队列前驱
volatile Node next; // 同步队列后继
volatile Thread thread; // 排队的线程
Node nextWaiter; // 双用途:EXCLUSIVE=独占模式标记 / 条件队列的后继指针
}
| waitStatus | 值 | 含义 | 谁来设置 |
|---|---|---|---|
| CANCELLED | 1 | 放弃等待(中断/超时) | cancelAcquire / transferForCancelledWait |
| SIGNAL | -1 | 后继线程已 park,等前驱释放后唤醒它 | shouldParkAfterFailedAcquire 把前驱置 SIGNAL |
| CONDITION | -2 | 在条件队列中 await | addConditionWaiter |
| PROPAGATE | -3 | 共享释放要传播 | doReleaseShared |
| 0 | 0 | 初始状态 | 节点入队时 |
关键认知 :SIGNAL 不是节点自己的状态,而是给前驱看的委托书 ------"前驱,你释放后记得唤醒我的线程"。等待线程 park 前,必须确认自己的前驱被标记为 SIGNAL(
shouldParkAfterFailedAcquire干的就是这件事),否则可能存在"前驱释放时不知道要叫你"的竞态。
head是哨兵节点 (thread == null),永远不代表等待线程,真正排队从head.next开始------这样队首线程被唤醒出队时,只需要把自己的后继设为新 head,删除自身,无需与队内其他节点竞争。
2.6 排队与出队机制
入队(addWaiter → enq):尾插 + CAS,失败重试直到成功:
java
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // mode = EXCLUSIVE / SHARED
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) { pred.next = node; return node; }
}
enq(node); // 队列空或 CAS 失败,走自旋版
return node;
}
private Node enq(final Node node) {
for (;;) { // 自旋直到成功
Node t = tail;
if (t == null) { // 队列为空:先初始化哨兵 head
if (compareAndSetHead(new Node())) continue;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) { t.next = node; return t; }
}
}
}
等锁(acquireQueued) ------被唤醒后不是直接拿锁,而是回到循环重新抢:
java
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { // ① 只有前驱是 head 才有资格抢
setHead(node); // ② 抢到:自己变成新哨兵 head,脱链
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // ③ 抢不到:给前驱挂牌 SIGNAL
parkAndCheckInterrupt()) // ④ 挂起自己;被唤醒后检查中断
interrupted = true;
}
} finally {
if (failed) cancelAcquire(node); // tryAcquire 抛异常才走这里(取消清理)
}
}
出队 :没有显式"删除"操作------抢到锁的节点把自己 setHead 成为新哨兵(head.thread = null),旧 head 靠 p.next = null 脱离引用后被 GC。队列里始终只留"哨兵 + 等待者"。
2.7 阻塞与唤醒 LockSupport
- 队内线程用
LockSupport.park()挂起,前驱释放锁后LockSupport.unpark(thread)唤醒------基于 Unsafe 的 park/unpark ,语义上等价于"每个线程一个许可(permit)":unpark发放许可(可提前发放,多次只算一次);park消费许可,没有许可就阻塞;若有线程被提前 unpark,park 立即返回不阻塞。
- 为什么不自旋?自旋空转烧 CPU。AQS 把排队等待做成 park 阻塞,唤醒开销换 CPU 节省,只有在入队 CAS、抢锁这种纳秒级窗口才自旋。
- 内存语义:
unpark之前线程做的事,对park返回后的线程可见(配合 volatile state 的读写构成 happens-before 链)。
2.8 公平锁与非公平锁
公平与否根本不在 AQS 里,而在于子类 tryAcquire 是否先看队列:
java
// ReentrantLock.FairSync ------ 抢之前先问"队列里有没有人排在我前面"
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && // 关键:队里没人(或队首就是我)才允许抢
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; // 重入 +1
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// ReentrantLock.NonfairSync ------ 进来先不管队列,直接 CAS 硬抢一次
final void lock() {
if (compareAndSetState(0, 1)) // 插队:能抢到就直接持锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 抢不到才走排队(nonfairTryAcquire 内部仍有重入判断)
}
非公平锁为什么更快?
刚释放锁的线程此时 CPU 缓存里还热着(线程亲和性),让它立刻再抢一次,可以省掉一次唤醒→调度→切换 的完整开销;排队唤醒属于"高成本路径"。代价是极端情况下队首线程可能被持续插队饿死(吞吐换公平)。
2.9 中断与超时支持
AQS 对中断有两套策略,取决于走哪个模板:
| 模板方法 | 中断处理 | 适用 |
|---|---|---|
acquire(arg) |
park 醒来发现中断位 → 记录 interrupted=true,继续排队抢锁 ,拿到锁后 selfInterrupt() 补回中断标志 |
Lock.lock() 的语义:不允许中断夺走锁,但中断状态会被保留 |
acquireInterruptibly(arg) |
park 中一旦被中断 → 立即抛 InterruptedException 并取消排队 | Lock.lockInterruptibly() |
tryAcquireNanos(arg, nanos) |
超时前反复抢;到点还没抢到 → cancelAcquire 出队返回 false |
Lock.tryLock(timeout, unit) |
parkAndCheckInterrupt() 里用 Thread.interrupted()(会清除标志 )而不是 isInterrupted():
java
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted(); // 必须清除:否则带着中断位再次 park 会立即返回,造成"忙等"
}
2.10 条件队列 Condition
ConditionObject 是 AQS 的内部类,对应 Object.wait/notify 的完整替代(一个锁可以 new 多个 Condition ,比内置 monitor 只有一个等待集强得多)。每个 Condition 维护一条单链表(用 Node.nextWaiter 串,头 firstWaiter / 尾 lastWaiter),节点状态为 CONDITION。
| 对比项 | Object.wait / notify | Condition.await / signal |
|---|---|---|
| 等待集数量 | 每个锁 1 个 | 每把锁可建多个(如"队满等"与"队空等"分开) |
| 中断响应 | 抛 InterruptedException | await 抛 / awaitUninterruptibly 不抛 |
| 超时 | 不支持精确纳秒 | awaitNanos / awaitUntil |
| 结构 | 对象监视器(monitor) | AQS 队列之外再加条件单链表 |
await 的设计精妙处在于:节点先挂进条件队列,再"完全释放"持有的锁 (释放重入计数次),然后 park;被 signal 后不会直接返回,而是先把自己塞回同步队列尾部重新排队抢锁,抢到才从 await 返回------保证了"await 返回时必然重新持有锁"的强语义。
3. 主线流程剖析 ★★★
3.1 独占获取加锁主线
以 ReentrantLock.lock()(默认非公平)为例,完整链路:
#mermaid-svg-bdOyjIbTifBf2TiV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bdOyjIbTifBf2TiV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bdOyjIbTifBf2TiV .error-icon{fill:#552222;}#mermaid-svg-bdOyjIbTifBf2TiV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bdOyjIbTifBf2TiV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bdOyjIbTifBf2TiV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bdOyjIbTifBf2TiV .marker.cross{stroke:#333333;}#mermaid-svg-bdOyjIbTifBf2TiV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bdOyjIbTifBf2TiV p{margin:0;}#mermaid-svg-bdOyjIbTifBf2TiV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster-label text{fill:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster-label span{color:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster-label span p{background-color:transparent;}#mermaid-svg-bdOyjIbTifBf2TiV .label text,#mermaid-svg-bdOyjIbTifBf2TiV span{fill:#333;color:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .node rect,#mermaid-svg-bdOyjIbTifBf2TiV .node circle,#mermaid-svg-bdOyjIbTifBf2TiV .node ellipse,#mermaid-svg-bdOyjIbTifBf2TiV .node polygon,#mermaid-svg-bdOyjIbTifBf2TiV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bdOyjIbTifBf2TiV .rough-node .label text,#mermaid-svg-bdOyjIbTifBf2TiV .node .label text,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape .label,#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape .label{text-anchor:middle;}#mermaid-svg-bdOyjIbTifBf2TiV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bdOyjIbTifBf2TiV .rough-node .label,#mermaid-svg-bdOyjIbTifBf2TiV .node .label,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape .label,#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape .label{text-align:center;}#mermaid-svg-bdOyjIbTifBf2TiV .node.clickable{cursor:pointer;}#mermaid-svg-bdOyjIbTifBf2TiV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bdOyjIbTifBf2TiV .arrowheadPath{fill:#333333;}#mermaid-svg-bdOyjIbTifBf2TiV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bdOyjIbTifBf2TiV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bdOyjIbTifBf2TiV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bdOyjIbTifBf2TiV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bdOyjIbTifBf2TiV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bdOyjIbTifBf2TiV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bdOyjIbTifBf2TiV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster text{fill:#333;}#mermaid-svg-bdOyjIbTifBf2TiV .cluster span{color:#333;}#mermaid-svg-bdOyjIbTifBf2TiV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bdOyjIbTifBf2TiV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bdOyjIbTifBf2TiV rect.text{fill:none;stroke-width:0;}#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape p,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bdOyjIbTifBf2TiV .icon-shape .label rect,#mermaid-svg-bdOyjIbTifBf2TiV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bdOyjIbTifBf2TiV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bdOyjIbTifBf2TiV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bdOyjIbTifBf2TiV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
失败(已被占)
成功
失败
是
否
被前驱释放时 unpark 唤醒
线程 T 调用 ReentrantLock.lock()
lock(): CAS state 0→1
✅ 持锁:exclusiveOwnerThread = T
不排队,直接返回
acquire(1) → tryAcquire 再试一次
(含重入判断:owner 是自己则 state+1)
addWaiter: 包装为独占 Node
CAS 尾插同步队列
acquireQueued 循环
前驱是 head
且 tryAcquire 成功?
setHead 自己成为新哨兵
旧 head 脱链 → 返回,持锁
shouldParkAfterFailedAcquire:
把前驱 CAS 标记为 SIGNAL
parkAndCheckInterrupt: park 挂起
逐步解释(对应源码编号):
- 快速路径 :非公平锁先无脑 CAS
0→1抢一次,命中即持锁,不建任何队列; - 慢路径 :CAS 失败 →
tryAcquire中检查是否重入 (owner == 当前线程则state+1,这也是为什么 lock 必须配 unlock 成对:每层 +1 必须 -1 回去); - 入队:重试也失败 → 生成独占 Node CAS 尾插(空队列先建哨兵 head);
- 等锁循环 :
acquireQueued中线程只关心两件事------我的前驱是不是 head(只有队首候补有资格抢)、抢不到就 park; - 挂牌 SIGNAL 后再 park :
shouldParkAfterFailedAcquire保证 park 前前驱已被标记,杜绝"前驱先释放了才想起没叫我"的窗口; - 唤醒即重赛 :被 unpark 后回到 for(;😉 重新 tryAcquire------被叫醒 ≠ 拿到锁,公平锁下不会输,但非公平锁下可能被新来的线程插队(于是再 park,属于正常竞争)。
3.2 独占释放唤醒主线
#mermaid-svg-Wx8MxxYEAalXL7AP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Wx8MxxYEAalXL7AP .error-icon{fill:#552222;}#mermaid-svg-Wx8MxxYEAalXL7AP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Wx8MxxYEAalXL7AP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Wx8MxxYEAalXL7AP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Wx8MxxYEAalXL7AP .marker.cross{stroke:#333333;}#mermaid-svg-Wx8MxxYEAalXL7AP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Wx8MxxYEAalXL7AP p{margin:0;}#mermaid-svg-Wx8MxxYEAalXL7AP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster-label text{fill:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster-label span{color:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster-label span p{background-color:transparent;}#mermaid-svg-Wx8MxxYEAalXL7AP .label text,#mermaid-svg-Wx8MxxYEAalXL7AP span{fill:#333;color:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .node rect,#mermaid-svg-Wx8MxxYEAalXL7AP .node circle,#mermaid-svg-Wx8MxxYEAalXL7AP .node ellipse,#mermaid-svg-Wx8MxxYEAalXL7AP .node polygon,#mermaid-svg-Wx8MxxYEAalXL7AP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Wx8MxxYEAalXL7AP .rough-node .label text,#mermaid-svg-Wx8MxxYEAalXL7AP .node .label text,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape .label,#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape .label{text-anchor:middle;}#mermaid-svg-Wx8MxxYEAalXL7AP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Wx8MxxYEAalXL7AP .rough-node .label,#mermaid-svg-Wx8MxxYEAalXL7AP .node .label,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape .label,#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape .label{text-align:center;}#mermaid-svg-Wx8MxxYEAalXL7AP .node.clickable{cursor:pointer;}#mermaid-svg-Wx8MxxYEAalXL7AP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Wx8MxxYEAalXL7AP .arrowheadPath{fill:#333333;}#mermaid-svg-Wx8MxxYEAalXL7AP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Wx8MxxYEAalXL7AP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Wx8MxxYEAalXL7AP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Wx8MxxYEAalXL7AP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Wx8MxxYEAalXL7AP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Wx8MxxYEAalXL7AP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster text{fill:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP .cluster span{color:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Wx8MxxYEAalXL7AP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Wx8MxxYEAalXL7AP rect.text{fill:none;stroke-width:0;}#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape p,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Wx8MxxYEAalXL7AP .icon-shape .label rect,#mermaid-svg-Wx8MxxYEAalXL7AP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Wx8MxxYEAalXL7AP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Wx8MxxYEAalXL7AP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Wx8MxxYEAalXL7AP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} state 未归 0(仍重入)
state 归 0 且 owner 清空
队列空或无人等
有等待者
线程 T 调用 unlock() → release(1)
tryRelease:
state-1,owner==自己校验
归 0 才算真正释放
返回 false,无事发生
释放成功 → head.waitStatus != 0 ?
什么都不做
unparkSuccessor(head)
把 head.waitStatus CAS 清 0
找 head 的下一个有效节点
next 无效/为空 → 从 tail 沿 prev 倒着找
跳过 CANCELLED
LockSupport.unpark(候选线程) → 回到 3.1 第 6 步重赛
java
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 清掉 SIGNAL/PROPAGATE
Node s = node.next;
if (s == null || s.waitStatus > 0) { // next 失效(被取消/断链)→ 必须倒着找
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) s = t; // 从队尾往前,取最靠近 head 的有效节点
}
if (s != null) LockSupport.unpark(s.thread);
}
关键认知 :唤醒目标严格限定"下一个排队的线程 "------这正是公平性的保障:AQS 保证排队顺序即唤醒顺序,插队只能发生在"没排队的线程 vs 正在重赛的队首"之间,已经 park 的队列次序不会被打破。
释放顺序必须是 先改 state、后唤醒(先 tryRelease 后 unparkSuccessor):保证被唤醒者读到的是释放后的新 state,且释放前的写操作对唤醒者可见。
3.3 共享模式获取与传播
共享模式与独占模式的核心差异:一个"成功"可以唤醒一串 。以 CountDownLatch 为例,最后一个 countDown 触发 gate 打开后,所有 await 线程都要被放行。
java
// 共享获取:tryAcquireShared 返回 >= 0 表示成功(无需排队)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 失败才进队
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r); // 成功 → 自己出队并"传播"
p.next = null;
if (interrupted) selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed) cancelAcquire(node);
}
}
传播链(setHeadAndPropagate → doReleaseShared):
java
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node); // 自己成为哨兵
// 还有剩余许可,或 head 状态表明有线程在等 → 继续唤醒下一个
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared(); // 唤醒下一个共享节点,形成链式放行
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // 抢标记失败就重试
unparkSuccessor(h); // 唤醒下一个
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // 避免"信号丢失"竞态
}
if (h == head) break; // head 没变才退出(唤醒后可能又来新人)
}
}
PROPAGATE 的来历(JDK 6 的 bug 修复) :曾有一类竞态------线程 A
releaseShared发现 head 状态为 0 直接退出,而新入队的线程 B 恰好把 head 置成 SIGNAL 之前,A 已错过传播窗口,导致 B 永远等不到唤醒。PROPAGATE 把"没有 SIGNAL 可清"的 head 标记为 -3,让后续 setHeadAndPropagate 判断waitStatus < 0时仍会继续传播,堵住这个洞。
3.4 Condition 完整链路
await 主流程(以 JDK 8~17 主线为准,细节随版本微调):
锁(state) 同步队列 条件队列(单链表) 等待线程 T1 锁(state) 同步队列 条件队列(单链表) 等待线程 T1 #mermaid-svg-5kkauE80xM6e07Y3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5kkauE80xM6e07Y3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5kkauE80xM6e07Y3 .error-icon{fill:#552222;}#mermaid-svg-5kkauE80xM6e07Y3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5kkauE80xM6e07Y3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5kkauE80xM6e07Y3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5kkauE80xM6e07Y3 .marker.cross{stroke:#333333;}#mermaid-svg-5kkauE80xM6e07Y3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5kkauE80xM6e07Y3 p{margin:0;}#mermaid-svg-5kkauE80xM6e07Y3 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5kkauE80xM6e07Y3 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5kkauE80xM6e07Y3 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-5kkauE80xM6e07Y3 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-5kkauE80xM6e07Y3 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-5kkauE80xM6e07Y3 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-5kkauE80xM6e07Y3 .sequenceNumber{fill:white;}#mermaid-svg-5kkauE80xM6e07Y3 #sequencenumber{fill:#333;}#mermaid-svg-5kkauE80xM6e07Y3 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-5kkauE80xM6e07Y3 .messageText{fill:#333;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5kkauE80xM6e07Y3 .labelText,#mermaid-svg-5kkauE80xM6e07Y3 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .loopText,#mermaid-svg-5kkauE80xM6e07Y3 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5kkauE80xM6e07Y3 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5kkauE80xM6e07Y3 .noteText,#mermaid-svg-5kkauE80xM6e07Y3 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-5kkauE80xM6e07Y3 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5kkauE80xM6e07Y3 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5kkauE80xM6e07Y3 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5kkauE80xM6e07Y3 .actorPopupMenu{position:absolute;}#mermaid-svg-5kkauE80xM6e07Y3 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-5kkauE80xM6e07Y3 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5kkauE80xM6e07Y3 .actor-man circle,#mermaid-svg-5kkauE80xM6e07Y3 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-5kkauE80xM6e07Y3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 另一线程 signal() await():包装 Node(CONDITION) 挂条件队尾 fullyRelease:释放全部重入次数(记 savedState) while(!isOnSyncQueue) LockSupport.park() transferForSignal:把 T1 从条件队列摘下 enq 塞回同步队列尾,恢复抢锁资格 park 期间被唤醒/重新排队抢锁 acquireQueued(savedState):抢到锁后从 await 返回
java
// ConditionObject.await 骨架(JDK 8,逻辑主线与 17 一致)
public final void await() throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
Node node = addConditionWaiter(); // ① 挂进条件队列(CONDITION 状态)
int savedState = fullyRelease(node); // ② 完全释放锁,记住重入次数
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // ③ 只要还没被挪回同步队列就 park
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) // ④ 重新抢锁
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) unlinkCancelledWaiters(); // ⑤ 清理已离开的等待者
if (interruptMode != 0) reportInterruptAfterWait(interruptMode);
}
// signal 只放行队头一个等待者
public final void signal() {
if (!isHeldExclusively()) throw new IllegalMonitorStateException(); // 必须持锁才能 signal
Node first = firstWaiter;
if (first != null) transferForSignal(first);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; // 已被取消则放弃
Node p = enq(node); // 塞回同步队列尾
int ws = p.waitStatus;
// 前驱已取消,或无法给前驱挂牌 SIGNAL → 直接 unpark,免得它永远睡死
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
理解要点:
- await 返回时必然重新持有锁 ------这是与
wait/notify最一致的语义(wait 返回也在 monitor 里); - 条件队列节点被 signal 后先排回同步队列尾,还要和其他线程竞争(非公平锁下可能再等一轮);
- 因此业务代码永远要
while (!条件) condition.await();而不是if------被唤醒不等于条件为真(虚假唤醒 + 竞争失败后条件已过期双重原因); signal只移一个,signalAll移全部(循环 transferForSignal);- await 中线程被中断有两种处理模式:THROW_IE(中断即取消等待抛异常)与 REINTERRUPT(拿到锁后再补中断)。
3.5 一次完整的锁竞争时序
场景:线程 A 持锁;B、C 陆续抢锁失败排队;A 释放;B 被唤醒拿锁:
volatile state 同步队列(CLH 变体) 线程 C 线程 B 线程 A volatile state 同步队列(CLH 变体) 线程 C 线程 B 线程 A #mermaid-svg-XF3KK8Xfr283qCyJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XF3KK8Xfr283qCyJ .error-icon{fill:#552222;}#mermaid-svg-XF3KK8Xfr283qCyJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XF3KK8Xfr283qCyJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XF3KK8Xfr283qCyJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XF3KK8Xfr283qCyJ .marker.cross{stroke:#333333;}#mermaid-svg-XF3KK8Xfr283qCyJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XF3KK8Xfr283qCyJ p{margin:0;}#mermaid-svg-XF3KK8Xfr283qCyJ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XF3KK8Xfr283qCyJ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XF3KK8Xfr283qCyJ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ .sequenceNumber{fill:white;}#mermaid-svg-XF3KK8Xfr283qCyJ #sequencenumber{fill:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-XF3KK8Xfr283qCyJ .messageText{fill:#333;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XF3KK8Xfr283qCyJ .labelText,#mermaid-svg-XF3KK8Xfr283qCyJ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .loopText,#mermaid-svg-XF3KK8Xfr283qCyJ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XF3KK8Xfr283qCyJ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XF3KK8Xfr283qCyJ .noteText,#mermaid-svg-XF3KK8Xfr283qCyJ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-XF3KK8Xfr283qCyJ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XF3KK8Xfr283qCyJ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XF3KK8Xfr283qCyJ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XF3KK8Xfr283qCyJ .actorPopupMenu{position:absolute;}#mermaid-svg-XF3KK8Xfr283qCyJ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-XF3KK8Xfr283qCyJ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XF3KK8Xfr283qCyJ .actor-man circle,#mermaid-svg-XF3KK8Xfr283qCyJ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-XF3KK8Xfr283qCyJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} lock() CAS 成功 state: 0→1(owner=A) 1 CAS 0→1 失败 2 addWaiter 尾插 node_B(head哨兵 → B) 3 tryAcquire 失败 4 尾插 node_C(head哨兵 → B → C) 5 shouldPark:把 head 挂 SIGNAL 后 park 6 shouldPark:把 B 挂 SIGNAL 后 park(等 B 放行) 7 unlock:state 1→0,owner 清空 8 unparkSuccessor(head):唤醒队首 B 9 醒来自检:前驱 == head 且 CAS state 0→1 成功 10 setHead(B),B 出队成为新哨兵(head → C) 11 继续 park,等 B 释放后再被唤醒 12
要点:唤醒严格按队列序(head.next → 新 head.next......),每个时刻队列里至多一个线程在被唤醒后重赛;出队只是"哨兵前进",无锁化,天然无 ABA 无死锁(所有修改都 CAS + park 语义保证)。
4. 类关系图 ★★★
4.1 JUC 继承全景
渲染错误: Mermaid 渲染失败: Parse error on line 75: ...RWS AQS <|-- TPE["ThreadPoolExecutor ----------------------^ Expecting 'NEWLINE', 'EOF', 'LABEL', got 'SQS'
4.2 AQS 内部结构
#mermaid-svg-WWSS7zLiwNnWlVpl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WWSS7zLiwNnWlVpl .error-icon{fill:#552222;}#mermaid-svg-WWSS7zLiwNnWlVpl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WWSS7zLiwNnWlVpl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WWSS7zLiwNnWlVpl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WWSS7zLiwNnWlVpl .marker.cross{stroke:#333333;}#mermaid-svg-WWSS7zLiwNnWlVpl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WWSS7zLiwNnWlVpl p{margin:0;}#mermaid-svg-WWSS7zLiwNnWlVpl g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-WWSS7zLiwNnWlVpl g.classGroup text .title{font-weight:bolder;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster-label text{fill:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster-label span{color:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster-label span p{background-color:transparent;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster text{fill:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .cluster span{color:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .nodeLabel,#mermaid-svg-WWSS7zLiwNnWlVpl .edgeLabel{color:#131300;}#mermaid-svg-WWSS7zLiwNnWlVpl .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-WWSS7zLiwNnWlVpl .label text{fill:#131300;}#mermaid-svg-WWSS7zLiwNnWlVpl .labelBkg{background:#ECECFF;}#mermaid-svg-WWSS7zLiwNnWlVpl .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-WWSS7zLiwNnWlVpl .classTitle{font-weight:bolder;}#mermaid-svg-WWSS7zLiwNnWlVpl .node rect,#mermaid-svg-WWSS7zLiwNnWlVpl .node circle,#mermaid-svg-WWSS7zLiwNnWlVpl .node ellipse,#mermaid-svg-WWSS7zLiwNnWlVpl .node polygon,#mermaid-svg-WWSS7zLiwNnWlVpl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WWSS7zLiwNnWlVpl .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl g.clickable{cursor:pointer;}#mermaid-svg-WWSS7zLiwNnWlVpl g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-WWSS7zLiwNnWlVpl g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-WWSS7zLiwNnWlVpl .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-WWSS7zLiwNnWlVpl .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-WWSS7zLiwNnWlVpl .dashed-line{stroke-dasharray:3;}#mermaid-svg-WWSS7zLiwNnWlVpl .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-WWSS7zLiwNnWlVpl #compositionStart,#mermaid-svg-WWSS7zLiwNnWlVpl .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #compositionEnd,#mermaid-svg-WWSS7zLiwNnWlVpl .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #dependencyStart,#mermaid-svg-WWSS7zLiwNnWlVpl .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #dependencyStart,#mermaid-svg-WWSS7zLiwNnWlVpl .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #extensionStart,#mermaid-svg-WWSS7zLiwNnWlVpl .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #extensionEnd,#mermaid-svg-WWSS7zLiwNnWlVpl .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #aggregationStart,#mermaid-svg-WWSS7zLiwNnWlVpl .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #aggregationEnd,#mermaid-svg-WWSS7zLiwNnWlVpl .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #lollipopStart,#mermaid-svg-WWSS7zLiwNnWlVpl .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl #lollipopEnd,#mermaid-svg-WWSS7zLiwNnWlVpl .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-WWSS7zLiwNnWlVpl .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-WWSS7zLiwNnWlVpl .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WWSS7zLiwNnWlVpl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WWSS7zLiwNnWlVpl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WWSS7zLiwNnWlVpl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} head/tail 串起同步队列
firstWaiter/lastWaiter 串起条件队列
Node
-volatile int waitStatus
-volatile Node prev
-volatile Node next
-volatile Thread thread
-Node nextWaiter
+CANCELLED = 1
+SIGNAL = -1
+CONDITION = -2
+PROPAGATE = -3
队列字段
-volatile Node head
-volatile Node tail
+compareAndSetHead()
+compareAndSetTail()
AbstractQueuedSynchronizer
-volatile int state
+getState() / setState()
+compareAndSetState()
4.3 同步队列与条件队列形态
#mermaid-svg-AL27Z8oxmJpe86xX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AL27Z8oxmJpe86xX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AL27Z8oxmJpe86xX .error-icon{fill:#552222;}#mermaid-svg-AL27Z8oxmJpe86xX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AL27Z8oxmJpe86xX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AL27Z8oxmJpe86xX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AL27Z8oxmJpe86xX .marker.cross{stroke:#333333;}#mermaid-svg-AL27Z8oxmJpe86xX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AL27Z8oxmJpe86xX p{margin:0;}#mermaid-svg-AL27Z8oxmJpe86xX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster-label text{fill:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster-label span{color:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster-label span p{background-color:transparent;}#mermaid-svg-AL27Z8oxmJpe86xX .label text,#mermaid-svg-AL27Z8oxmJpe86xX span{fill:#333;color:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .node rect,#mermaid-svg-AL27Z8oxmJpe86xX .node circle,#mermaid-svg-AL27Z8oxmJpe86xX .node ellipse,#mermaid-svg-AL27Z8oxmJpe86xX .node polygon,#mermaid-svg-AL27Z8oxmJpe86xX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AL27Z8oxmJpe86xX .rough-node .label text,#mermaid-svg-AL27Z8oxmJpe86xX .node .label text,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape .label,#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape .label{text-anchor:middle;}#mermaid-svg-AL27Z8oxmJpe86xX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AL27Z8oxmJpe86xX .rough-node .label,#mermaid-svg-AL27Z8oxmJpe86xX .node .label,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape .label,#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape .label{text-align:center;}#mermaid-svg-AL27Z8oxmJpe86xX .node.clickable{cursor:pointer;}#mermaid-svg-AL27Z8oxmJpe86xX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AL27Z8oxmJpe86xX .arrowheadPath{fill:#333333;}#mermaid-svg-AL27Z8oxmJpe86xX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AL27Z8oxmJpe86xX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AL27Z8oxmJpe86xX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AL27Z8oxmJpe86xX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AL27Z8oxmJpe86xX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AL27Z8oxmJpe86xX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AL27Z8oxmJpe86xX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster text{fill:#333;}#mermaid-svg-AL27Z8oxmJpe86xX .cluster span{color:#333;}#mermaid-svg-AL27Z8oxmJpe86xX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AL27Z8oxmJpe86xX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AL27Z8oxmJpe86xX rect.text{fill:none;stroke-width:0;}#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape p,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AL27Z8oxmJpe86xX .icon-shape .label rect,#mermaid-svg-AL27Z8oxmJpe86xX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AL27Z8oxmJpe86xX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AL27Z8oxmJpe86xX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AL27Z8oxmJpe86xX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 条件队列(单链表,nextWaiter 串,每条 Condition 一个)
同步队列(双向 CLH 变体,volatile prev/next)
signal 后 transferForSignal
从条件队列摘除 enq 回同步队列尾
head 哨兵
thread=null
Node
thread=B
waitStatus=SIGNAL(挂到前驱上)
Node
thread=C
waitStatus=SIGNAL
Node
thread=D 等待中
tail
Node
thread=E
waitStatus=CONDITION
Node
thread=F
waitStatus=CONDITION
- 同步队列双向:往前能找前驱(判断 head)、往后能快速唤醒后继,且能应对取消断链;
- 条件队列单向:只需"从头部摘取"这一个操作;
- 一个线程同一时刻只会在一条队列里:await 时在条件队列,被 signal 后先回同步队列,抢到锁出队执行。
5. 动手实现一个同步器
AQS 官方文档的经典用法:只需要实现 acquire/release 的"判定逻辑",排队、阻塞、唤醒、取消清理全部白送。
5.1 基于 AQS 的自定义互斥锁
java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
/** 基于 AQS 的互斥锁:不可重入,非公平(官方 javadoc 同款思路) */
public final class MyLock {
private final Sync sync = new Sync();
private static final class Sync extends AbstractQueuedSynchronizer {
@Override protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) { // state 0→1 成功即持锁
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false; // 没抢到 → AQS 帮我们排队
}
@Override protected boolean tryRelease(int releases) {
if (getState() == 0) throw new IllegalMonitorStateException(); // 未持锁释放
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException(); // 谁持有谁释放(无所有权转移)
setExclusiveOwnerThread(null);
setState(0); // volatile 写,使之前的写可见
return true; // 返回 true 才触发唤醒后继
}
@Override protected boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
}
public void lock() { sync.acquire(1); } // 不可中断版
public void unlock() { sync.release(1); }
public boolean tryLock() { return sync.tryAcquire(1); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
public boolean isLocked() { return sync.isLocked(); }
}
注意 :此锁不可重入 ------若持有线程再次 lock(),tryAcquire 必然 false,线程把自己排进队列去等"自己释放",形成自死锁。这正是 AQS 版本之简洁:重入与否只取决于 tryAcquire 里加不加"owner 是自己就 +1"的代码。
5.2 升级为可重入与公平
java
/** 把 tryAcquire 换成下面的逻辑,立即变成可重入 + 可选公平 */
private static final class FairSync extends AbstractQueuedSynchronizer {
@Override protected boolean tryAcquire(int acquires) {
final Thread cur = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平性只加这一行:队列里有更早的等待者就让行
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(cur);
return true;
}
} else if (cur == getExclusiveOwnerThread()) {
int nextc = c + acquires; // 重入计数 +1
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
@Override protected boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = (c == 0); // 计数归 0 才算真释放
if (free) setExclusiveOwnerThread(null);
setState(c);
return free;
}
}
AQS 的通用套路一目了然 :可重入 = 记录持有者 + 计数加减;公平 = tryAcquire 前先 hasQueuedPredecessors();锁语义全部收敛在这 1~2 个方法里。
5.3 基于 AQS 实现共享闸门 MyLatch
java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
/** 共享模式:CountDownLatch 同款语义的倒计数闸门 */
public final class MyLatch {
private final Sync sync;
public MyLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public void await() { sync.acquireSharedInterruptibly(1); } // 门没开就全部排队
public boolean await(long millis) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, millis * 1_000_000L); // 超时版
}
public void countDown() { sync.releaseShared(1); } // 每次减 1,归 0 开门
public long getCount() { return sync.getCount(); }
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) { setState(count); }
@Override protected int tryAcquireShared(int arg) {
return getState() == 0 ? 1 : -1; // 门没开(state>0)→ 负数 → AQS 帮我们排队
}
@Override protected boolean tryReleaseShared(int arg) {
for (;;) { // CAS 自旋减一
int c = getState();
if (c == 0) return false; // 已经开门,再减无意义
int nextc = c - 1;
if (compareAndSetState(c, nextc)) return nextc == 0; // 只有"减到 0"那一下返回 true → 唤醒全部
}
}
}
}
共享模式的两个精妙点 :所有 await 线程都排同一个队,但只有最后一个 countDown 者返回 true 触发 releaseShared → doReleaseShared 的链式放行;即使 100 个线程在等,也只需一次传播即可全部唤醒。
5.4 对照与验证 Demo
java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/** 对照验证:MyLock(独占)与 MyLatch(共享)的正确性与自旋阻塞行为 */
public class AqsDemo {
static final int THREADS = 16;
static final int PER_THREAD = 50_000;
static final AtomicInteger counter = new AtomicInteger();
public static void main(String[] args) throws Exception {
demoMutex();
demoUnfairnessObservation();
demoMyLatch();
}
/** ① 独占锁保护计数:结果必须恰好 = THREADS * PER_THREAD */
static void demoMutex() throws InterruptedException {
MyLock lock = new MyLock();
ExecutorService pool = Executors.newFixedThreadPool(THREADS);
CountDownLatch done = new CountDownLatch(THREADS);
for (int t = 0; t < THREADS; t++) {
pool.execute(() -> {
for (int i = 0; i < PER_THREAD; i++) {
lock.lock();
try { counter.incrementAndGet(); } // 临界区:非原子自增,靠锁互斥
finally { lock.unlock(); }
}
done.countDown();
});
}
done.await();
pool.shutdown();
int expect = THREADS * PER_THREAD;
System.out.println("① MyLock 计数结果 = " + counter.get() + "(期望 " + expect + ")"
+ (counter.get() == expect ? " ✅ 互斥生效" : " ❌ 有丢失"));
}
/** ② 观察等待线程真实入队:抢不到锁的线程会阻塞在 AQS 队列里(而非自旋烧 CPU) */
static void demoUnfairnessObservation() throws Exception {
MyLock lock = new MyLock();
lock.lock(); // 主线程先占住锁
AtomicInteger entered = new AtomicInteger();
ExecutorService pool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 2; i++) {
pool.execute(() -> {
lock.lock();
entered.incrementAndGet();
lock.unlock();
});
}
Thread.sleep(100); // 给两个线程时间尝试抢锁
System.out.println("② 主线程持锁时,队列中等待线程数 = " + lock.hasQueuedThreads()
+ "(1~2 之间,0 则说明被主线程插队吃掉)");
lock.unlock(); // 释放,唤醒队列
pool.shutdown();
pool.awaitTermination(1, TimeUnit.SECONDS);
System.out.println(" 释放后进入临界区线程数 = " + entered.get() + " ✅");
}
/** ③ MyLatch 闸门:所有子线程 countDown 后,主线程 await 才放行 */
static void demoMyLatch() throws InterruptedException {
int n = 8;
MyLatch latch = new MyLatch(n);
ExecutorService pool = Executors.newFixedThreadPool(n);
for (int i = 0; i < n; i++) {
final int id = i;
pool.execute(() -> {
try { Thread.sleep(20 + id * 10); } // 模拟不同耗时
catch (InterruptedException ignored) {}
System.out.println(" worker-" + id + " 完工,countDown");
latch.countDown();
});
}
latch.await(); // 阻塞直到 8 个都 countDown
System.out.println("③ 8 个 worker 全部完成后主线程才走到这里 ✅");
pool.shutdown();
}
}
运行预期输出:
text
① MyLock 计数结果 = 800000(期望 800000) ✅ 互斥生效
② 主线程持锁时,队列中等待线程数 = 2(1~2 之间,0 则说明被主线程插队吃掉)
释放后进入临界区线程数 = 2 ✅
worker-0 完工,countDown
...
③ 8 个 worker 全部完成后主线程才走到这里 ✅
💡 实验建议:把 5.2 的公平版 tryAcquire 换进 MyLock 再跑 ②------
hasQueuedThreads稳定为 2 且无插队;把锁换成new ReentrantLock(false)反复跑 ②,偶尔能看到 0(主线程刚释放就被新线程插队),这正是非公平的运行时证据。
6. 扩展点与常见问题 FAQ
6.1 高频面试问题速答
Q1:AQS 为什么用一个 int state 而不是 boolean?
因为同步语义远不止"有/无":重入需要计数(ReentrantLock)、许可需要计数(Semaphore)、两个维度可以按位切分(读写锁高 16/低 16 位)、倒计数门闩需要递减(CountDownLatch)。int + volatile + CAS 是"一个原子变量表达任意同步条件"的最小完备集。
Q2:线程拿到锁失败后,为什么 park 而不是自旋?
自旋只适合临界区极短的纳秒级等待;AQS 的服务对象是"任意长的持锁时间",自旋会空耗 CPU。AQS 只在 CAS 入队/抢锁这类确定极短的窗口自旋,真正的等待一律 LockSupport.park 挂起,释放 CPU。
Q3:为什么唤醒的是 head 的下一个节点,而不是任意一个?
队列就是"先来后到"的秩序本身。若唤醒者任意挑人,公平性无从谈起,且需要在队列中做复杂的随机删除。只唤醒 head.next(严格队首候补),队首抢到后自己出队变为新 head,秩序自然滚动,出队 O(1)。
Q4:为什么 unparkSuccessor 从 tail 往前找,而不是直接找 next?
被取消的节点会把 next 指向自己(断链),且取消时可能正好处于"prev 已连、next 未连"的中间态------next 方向随时可能断;而 prev 方向只要节点还在队列里就必然有效。所以从 tail 沿 prev 逆扫,能保证找到"最靠近 head 的有效节点"。
Q5:非公平锁真的"不公平"吗?会饿死吗?
只是"新来的线程可以和队首重赛一次/多次"。已经 park 的队列次序永远不被打破,所以最多是队首被多插队几次;在极端持续竞争下理论上有饥饿可能,实际中由于新线程也要消耗一次 CAS/调度机会,概率极低。非公平的吞吐优势来自线程亲和性(刚释放锁的线程缓存还是热的,省一次唤醒切换)。
Q6:synchronized 与 AQS(显式锁)怎么选?
| 维度 | synchronized | ReentrantLock(AQS) |
|---|---|---|
| 获取方式 | JVM 管程(偏向/轻量/重量级升级) | CAS + 队列 + park 自管理 |
| 可中断/超时 | 不支持 | lockInterruptibly / tryLock(timeout) |
| 公平 | 无公平概念 | 可构造公平锁 |
| 多条件等待 | 每锁 1 个等待集 | newCondition() 多个 |
| 灵活性/可扩展 | 不可扩展 | 继承 AQS 可自定义同步器 |
| 崩溃安全 | 异常自动释放(monitorexit) | 必须 finally unlock(JDK17 起有 try-with-resources 风格的 Lock 支持改善) |
| 性能 | 无竞争时开销极低(偏向锁) | 无竞争时 CAS 一次即返回 |
结论:无竞争或简单互斥用 synchronized(JVM 持续优化);需要超时、可中断、多条件、读多写少(读写锁)、自定义同步语义时用 AQS 家族。JDK 21+ 的虚拟线程下锁竞争模型又有变化,这是另一个话题。
Q7:await 被唤醒后为什么要 while 循环重新检查条件?
三层原因:① 虚假唤醒(spurious wakeup)是 park 的合法行为;② 从 signal 到真正抢到锁之间,别的线程可能又改回了条件(条件在锁内判断,抢到锁才能重新看 );③ await 返回时锁已重新持有,必须重新验证才能安全消费。while (!cond) cond.await() 是铁律。
6.2 源码细节点辨析
D1:为什么 acquire() 中 park 醒来要 Thread.interrupted() 并且最后 selfInterrupt()?
lock() 的语义是"中断不能阻止我拿锁"。park 响应中断会立即返回,若不清除中断位,下次 park 又会立刻返回 → 变成忙等;所以 parkAndCheckInterrupt 用 interrupted()(带清除)取回"是否被中断过"并记下。拿到锁后 selfInterrupt() 把中断标志补回去,让上层代码仍能看到这次中断。
D2:head 为什么要设计成 thread == null 的哨兵?
① 队空判断 O(1):head == tail 即空;② 出队零竞争:旧 head 直接把后继设为新 head,无需在队列里删除节点;③ 避免"队列空但线程刚准备入队"时的 head/tail 双写竞争。代价是多一个虚拟节点。
D3:读锁的"共享重入"怎么实现?CountDownLatch 不需要的东西它为什么需要?
Semaphore/CountDownLatch 的共享计数是全局 的,state 一个 int 足够;但读锁要求"每个线程单独记录自己重入了几次"(释放时只能减自己的)。所以 ReentrantReadWriteLock.Sync 在全局 state>>>16 之外,另用 ThreadLocalHoldCounter(ThreadLocal) 给每个读线程存自己的 HoldCounter(含首次获取标志 firstReader 优化)。这说明:state 表达"全局还剩多少",线程级账本交给 ThreadLocal。
D4:写锁获取里为什么 state 高 16 位读锁归零才允许(c != 0 && w == 0 则失败)?
写锁是独占的:一旦有任何读锁持有(state 高位非 0),写锁必须等待------否则写者改数据时读者读到一半。反过来读锁获取时若写锁被独占(低位非 0)也要等。这就是"读写互斥、读读共享"在 state 位运算上的全部表达:c >>> 16 != 0 有读者、c & 0xFFFF != 0 有写者。
D5:acquire 期间 tryAcquire 抛异常会发生什么?
acquireQueued 的 finally 里 failed == true 触发 cancelAcquire:把自己标记 CANCELLED、从队列剔除(尾节点则 CAS 摘 tail,中间节点则让前驱跳过后继),必要时唤醒后继。这套"异常安全"保证 AQS 队列永远不会因为某个同步器子类的 bug 而整队卡死。
D6:FutureTask 为什么在 JDK 8 放弃 AQS?
AQS 是按"获取/释放"语义设计的通用框架,对 FutureTask 而言它的"状态迁移"(NEW→COMPLETING→NORMAL/CANCELLED...)加上 waiters 栈其实只需要一小部分能力;AQS 引入额外的队列维护开销。JDK 8 用自研 volatile int state + Unsafe CAS + Treiber 栈实现等值更精简,也说明模板框架不是零成本的------选型时要考虑"资源争夺型 vs 状态机型"。
6.3 扩展实践方向与总结
还能用 AQS 造什么?(写代码前先想清楚 state 语义即可)
| 需求 | 方案 |
|---|---|
| 一次性放行的闸门(多个线程等一个事件) | 共享模式,state 0/1 门 |
| 允许 N 个并发、超出排队(限流器) | Semaphore 即此模型 |
| 多读单写(缓存场景) | ReentrantReadWriteLock 直接可用 |
| 等待队列不按 FIFO(如"最短等待时间者先执行") | AQS 之外自研(队列顺序是 AQS 固定的) |
| 纯状态机任务(一次性任务、阶段计数) | 不一定要 AQS------参考 JDK8 FutureTask 自研 state 机 |
总结(一句话复习):
AQS = 模板方法(子类决定"能不能") + volatile state(同步条件的载体) + CLH 变体队列("不能"时的排队与唤醒秩序)。
独占模式唤醒严格队首一个;共享模式成功一次传播一串;Condition 是"排队中的排队"------条件队列等 signal,被 signal 后回到同步队列等锁。
想真正吃透它:读一遍 acquire/release/acquireQueued 三个方法,再抄 5.1 的 MyLock 跑一遍多线程,胜过背十篇八股。
学习路径建议:AQS 原理 → ReentrantLock 源码(看两个 Sync 差异)→ ReentrantReadWriteLock(看 state 位拆分与 HoldCounter)→ Semaphore/CountDownLatch(共享传播)→ Condition 源码 → 自研一个同步器验证 → 回到本文对照 FAQ 查漏。配套阅读 JMM(volatile/happens-before)与 LockSupport 内存语义。