AQS
基于 JDK 1.8 源码的 AQS 核心骨架与基础架构分析。
一、核心架构设计
1.1 类继承关系
public abstract class AbstractOwnableSynchronizer {
private transient Thread exclusiveOwnerThread;
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer {
private volatile int state;
private transient volatile Node head;
private transient volatile Node tail;
}1.2 State 变量
private volatile int 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);
}| 设计点 | 说明 |
|---|---|
| 为何是 int | 32 位足够表示大多数场景(锁重入次数、信号量许可数等);int 操作原子性更好 |
| volatile 作用 | 保证多线程间的可见性,一个线程修改 state 其他线程立即可见 |
| CAS 操作 | 无锁并发修改 state,避免加锁开销,多线程竞争时只有一个成功 |
state 在不同同步器中的语义:
| 同步器 | state 含义 |
|---|---|
| ReentrantLock | 重入次数(0=未锁定,>0=重入次数) |
| Semaphore | 剩余许可数 |
| CountDownLatch | 剩余计数 |
| ReentrantReadWriteLock | 高16位=读锁持有数,低16位=写锁重入数 |
1.3 资源拥有者
// AbstractOwnableSynchronizer
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}| 特性 | 说明 |
|---|---|
| 定义位置 | AQS 的父类 AbstractOwnableSynchronizer |
| 作用 | 记录当前持有独占锁的线程 |
| 用途 | 可重入判断、isHeldByCurrentThread() 等 |
1.4 CLH 队列结构
private transient volatile Node head;
private transient volatile Node tail;
为什么是双向链表?
| 原因 | 说明 |
|---|---|
| 取消节点需要 prev | 取消的节点需要找到前驱,将前驱的 next 指向自己的后继 |
| acquireQueued 需要判断前驱 | 只有前驱是 head 的节点才有资格尝试获取锁 |
| 唤醒后继需要 next | 释放锁时需要找到后继节点进行唤醒 |
Head 作为"虚节点":
private void setHead(Node node) {
head = node;
node.thread = null; // 清空线程引用
node.prev = null; // 断开前驱
}- head 始终是一个"虚节点",不代表任何等待线程
- 当线程获取锁成功后,将自己设为 head,并清空 thread 引用
- 真正等待的线程从 head.next 开始
入队操作(CAS + 自旋):
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
// 队列为空,初始化(创建虚拟 head)
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 尾插法
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}二、Node 节点详解
2.1 Node 类定义
static final class Node {
// 模式标记
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// 等待状态
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}2.2 模式标记
| 标记 | 值 | 含义 |
|---|---|---|
| EXCLUSIVE | null | 独占模式(如 ReentrantLock) |
| SHARED | new Node() | 共享模式(如 Semaphore、CountDownLatch) |
// 判断是否共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}2.3 等待状态 (waitStatus)
| 状态 | 值 | 含义 | 使用场景 |
|---|---|---|---|
| INITIAL | 0 | 初始状态 | 新创建的节点 |
| SIGNAL | -1 | 后继需要唤醒 | 当前节点释放锁时需要 unpark 后继 |
| CANCELLED | 1 | 已取消 | 线程超时或被中断,需要从队列移除 |
| CONDITION | -2 | 在 Condition 队列中 | await() 后节点进入 Condition 队列 |
| PROPAGATE | -3 | 传播唤醒 | 共享模式下确保唤醒能传播 |
注意:负值表示节点处于有效等待状态,正值(CANCELLED=1)表示节点已取消。
// 判断节点是否有效等待
if (ws < 0) { /* 有效 */ }
if (ws > 0) { /* 已取消 */ }2.4 指针字段
| 字段 | 用途 |
|---|---|
| prev | 指向前驱节点,用于取消操作和判断是否可以获取锁 |
| next | 指向后继节点,用于释放锁时唤醒后继 |
| nextWaiter | 两个用途(见下表) |
nextWaiter 的双重用途:
| 场景 | nextWaiter 含义 |
|---|---|
| CLH 队列中 | 标记模式:SHARED 或 EXCLUSIVE (null) |
| Condition 队列中 | 指向下一个等待节点(单向链表) |
三、两种资源模式的抽象
3.1 钩子方法对比
独占模式:
// 需要子类实现
protected boolean tryAcquire(int arg) // 返回 boolean
protected boolean tryRelease(int arg) // 返回 boolean共享模式:
// 需要子类实现
protected int tryAcquireShared(int arg) // 返回 int
protected boolean tryReleaseShared(int arg) // 返回 boolean3.2 返回值含义
| 方法 | 返回值 | 含义 |
|---|---|---|
| tryAcquire | boolean | true=获取成功,false=获取失败 |
| tryAcquireShared | int | 负数=失败,0=成功但无剩余,正数=成功且有剩余资源 |
tryAcquireShared 返回 int 的意义:
// 返回值 > 0 表示还有剩余资源,可以继续唤醒后续等待的共享节点
if (tryAcquireShared(arg) >= 0) {
// 获取成功
if (返回值 > 0 && 后继是共享节点) {
// 传播唤醒
}
}3.3 唤醒机制差异
独占模式 - 单点唤醒:
释放锁 → 唤醒 head.next → 结束
只唤醒一个节点public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 只唤醒一个后继
return true;
}
return false;
}共享模式 - 传播唤醒:
释放锁 → 唤醒 head.next → 该节点获取成功 → 继续唤醒下一个共享节点 → ...
链式传播唤醒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) // head 没变则退出,否则继续传播
break;
}
}PROPAGATE 的作用:
防止"唤醒丢失"问题:
场景:多个线程同时释放共享资源
线程A:releaseShared → 唤醒 B
线程B:获取成功,准备唤醒 C
线程C:此时可能被漏掉
PROPAGATE 状态确保即使 head.waitStatus 已经是 0,
也会被设置为 PROPAGATE,让后续节点知道需要继续传播四、模板方法模式
4.1 AQS 帮我们做了什么
| 功能 | AQS 实现 |
|---|---|
| 入队管理 | addWaiter()、enq() - CAS 安全入队 |
| 阻塞/唤醒 | LockSupport.park()/unpark() |
| 自旋获取 | acquireQueued() - 自旋 + 阻塞 |
| 取消处理 | cancelAcquire() - 清理取消节点 |
| Condition 支持 | ConditionObject 内部类 |
4.2 我们需要做什么
只需重写以下钩子方法(根据需要选择):
| 钩子方法 | 模式 | 含义 |
|---|---|---|
| tryAcquire(int) | 独占 | 尝试获取资源 |
| tryRelease(int) | 独占 | 尝试释放资源 |
| tryAcquireShared(int) | 共享 | 尝试共享获取 |
| tryReleaseShared(int) | 共享 | 尝试共享释放 |
| isHeldExclusively() | 通用 | 是否被当前线程独占 |
4.3 模板方法示意
// AQS 的模板方法
public final void acquire(int arg) {
// 1. 尝试获取(子类实现)
if (!tryAcquire(arg) &&
// 2. 获取失败则入队(AQS 实现)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 3. 处理中断(AQS 实现)
selfInterrupt();
}┌────────────────────────────────────────────────────────┐
│ AQS 框架 │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ acquire() / release() / acquireShared() ... │ │
│ │ 模板方法 (final) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ │ 调用 │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ tryAcquire() / tryRelease() ... │ │
│ │ 钩子方法 (protected) │ │
│ │ 由子类实现 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘五、总结
核心结构速览
| 组件 | 说明 |
|---|---|
| state | 同步状态,volatile int,CAS 修改 |
| exclusiveOwnerThread | 持有独占锁的线程 |
| CLH 队列 | 双向链表,head 是虚节点 |
| Node | 队列节点,包含 waitStatus、模式标记、指针 |
waitStatus 速查
| 值 | 名称 | 含义 |
|---|---|---|
| 0 | INITIAL | 初始状态 |
| -1 | SIGNAL | 后继需要唤醒 |
| -2 | CONDITION | 在 Condition 队列中 |
| -3 | PROPAGATE | 共享模式传播唤醒 |
| 1 | CANCELLED | 已取消 |
模式对比
| 对比项 | 独占模式 | 共享模式 |
|---|---|---|
| 钩子返回值 | boolean | int |
| 唤醒机制 | 单点唤醒 | 传播唤醒 |
| 典型实现 | ReentrantLock | Semaphore、CountDownLatch |
六、个人复述理解
6.1 核心执行流程概述
AQS(AbstractQueuedSynchronizer)的核心是基于模板方法模式设计的:它定义了资源管理(State)和线程排队(CLH 队列)的通用骨架,而具体的抢锁(tryAcquire)和释放(tryRelease)逻辑则留给子类实现。
6.2 获取锁与入队流程
当线程发起抢锁时,AQS 会调用子类的
tryAcquire。如果成功,线程直接执行;如果失败,线程会被封装成一个 Node 节点,通过 CAS 尾插法进入 CLH 队列。这是一个双向链表,负责管理所有等待资源的线程。节点的核心变量是
waitStatus,其关键状态包括:
- 1 (CANCELLED):线程取消排队
- -1 (SIGNAL):当前节点释放锁时必须唤醒后继节点
- -2 (CONDITION):节点在 Condition 等待队列中
- -3 (PROPAGATE):共享模式下的唤醒传播
6.3 自旋与阻塞 (acquireQueued)
节点入队后进入自旋(死循环)。在循环中,线程首先判断自己的前驱节点是否为 Head。如果是,说明它是队列里的"老二",有资格再次尝试
tryAcquire抢锁。
- 如果抢成功,它将自己设为 Head 并断开原 Head 的连接
- 如果抢失败,或者前驱不是 Head,它会检查前驱的
waitStatus如果前驱状态是 0,则将其 CAS 修改为 -1 (SIGNAL),表示"我睡了,你走的时候叫醒我"。修改成功后,如果再次抢锁失败,线程调用
LockSupport.park()进入阻塞状态,等待被唤醒。
6.4 释放与唤醒 (release)
当持有锁的线程调用
tryRelease成功(State 归零)后,它会执行唤醒逻辑。AQS 获取 Head 节点,理论上应该唤醒head.next。但为了防止并发下的链表断裂或节点失效(如状态为 CANCELLED),AQS 采用兜底机制:如果 next 节点为空或已取消,则从 Tail(尾部)开始向前遍历,找到最靠近 Head 的那个非取消状态的节点,调用
unpark唤醒它。从尾部向前遍历的原因:入队操作不是原子的(先设 prev,再 CAS tail,最后设 next),在 next 尚未设置时,从前向后遍历可能找不到节点,但 prev 已经设置好了,所以从尾部向前遍历更可靠。
6.5 Condition 条件队列机制
Condition 队列是一个独立的单向链表(使用
nextWaiter指针连接)。
- await():线程会释放所有持有的锁资源(包括重入次数),将自己封装成状态为 -2 (CONDITION) 的节点进入 Condition 队列尾部,并阻塞挂起
- signal():Condition 队列的头节点会被取出,其状态通过 CAS 从 -2 修改为 0,然后通过
enq()方法转移(Transfer)到 CLH 队列的尾部。转移成功后,该线程恢复到正常的 CLH 排队抢锁流程中
