AbstractQueuedSynchronizer
(简称 AQS)是 Java 并发包中的核心组件之一,它为实现依赖于同步状态的锁和同步器提供了一个框架。像 ReentrantLock
、CountDownLatch
、Semaphore
等都是基于 AQS 实现的。
一、概述
-
同步状态 (state)
:
通过 volatile int state 表示资源状态(如 ReentrantLock 中 0=未锁定,>0=锁定次数) -
CLH 变体队列
:
双向链表构成的 FIFO 线程等待队列,节点保存阻塞线程和等待状态 -
模板方法模式
:
子类只需实现 tryAcquire/tryRelease 等tryXXX状态控制方法
二、 源码
1. 核心字段
// AQS 核心字段
private transient volatile Node head; // 队列头节点(哨兵)
private transient volatile Node tail; // 队列尾节点
private volatile int state; // 同步状态
2. 静态内部类 Node
static final class Node {
// 表示共享模式
static final Node SHARED = new Node();
// 表示独占模式
static final Node EXCLUSIVE = null;
// 线程等待状态字段
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 关联的线程
volatile Thread thread;
// 下一个等待者
Node nextWaiter;
}
waitStatus
有以下几种状态:
CANCELLED (1)
:线程已取消。SIGNAL (-1)
:后续线程需要被唤醒。CONDITION (-2)
:节点在条件队列中。PROPAGATE (-3)
:适用于共享模式,传播唤醒。
三、方法分类
AQS 提供了两组 API:
1. 模板方法(由 AQS 实现)
acquire(int arg)
:独占式获取同步状态。release(int arg)
:独占式释放同步状态。acquireShared(int arg)
:共享式获取同步状态。releaseShared(int arg)
:共享式释放同步状态。
这些方法内部调用钩子方法(需子类实现)
2. 钩子方法(由子类实现)
tryAcquire(int arg)
:独占式获取同步状态。tryRelease(int arg)
:独占式释放同步状态。tryAcquireShared(int arg)
:共享式获取同步状态。tryReleaseShared(int arg)
:共享式释放同步状态。isHeldExclusively()
:当前同步器是否被当前线程独占。
四、核心方法
acquire(...)
方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 1. 尝试获取资源(子类实现)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 2. 创建节点并入队
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
// 3. 队列中等待获取资源
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // GC assist
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
// 4. 检查是否需要阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // -1
return true;
if (ws > 0) { // CANCELLED
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
// 5. 阻塞并检查中断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
说明
-
快速路径尝试
- 首先调用
tryAcquire()
(子类实现)尝试直接获取资源 - 成功则立即返回(无锁操作)
- 首先调用
-
节点入队
- 创建
EXCLUSIVE
节点(包含当前线程) - 使用CAS自旋确保线程安全入队
- 队列为空时初始化虚拟头节点(head≠null保证算法健壮性)
- 创建
-
队列中自旋
- 头节点检查优化:仅当头节点后继才尝试获取
- 状态传播机制:
- 将前驱状态设为
SIGNAL
(-1)(表示需唤醒) - 清理
CANCELLED
节点保证链表连续
- 将前驱状态设为
-
阻塞控制
- 调用
LockSupport.park()
精准挂起线程 - 唤醒后检查中断状态但不响应(留到获取后处理)
- 调用
-
中断处理
- 阻塞期间中断只做标记(
Thread.interrupted()
) - 最终获取资源后调用
selfInterrupt()
补发中断
- 阻塞期间中断只做标记(
设计
- 无锁快速路径:
tryAcquire
无竞争时直接成功 - CAS队列操作:避免入队时的全局锁
- 有限自旋:至少两次
tryAcquire
机会(入队前+成为头节点时) - 精准唤醒:仅当
waitStatus=SIGNAL
时才唤醒 - 中断延迟响应:保证锁获取的原子性
确保:
- 公平性:严格FIFO顺序
- 活性:取消节点自动清理
流程图
release(...)
方法
public final boolean release(int arg) {
if (tryRelease(arg)) { // 1. 尝试释放资源
Node h = head; // 2. 获取头节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 3. 唤醒后继节点
return true;
}
return false;
}
// 子类实现的资源释放逻辑
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// 唤醒后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) // 清除SIGNAL状态
node.compareAndSetWaitStatus(ws, 0);
Node s = node.next;
// 1. 直接后继无效时,从尾向前查找
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t; // 找到第一个未取消的节点
}
// 2. 唤醒线程
if (s != null)
LockSupport.unpark(s.thread);
}
说明
- 资源释放阶段
tryRelease()
由子类实现资源释放逻辑- 返回
true
表示资源完全释放(如state=0
)
- 唤醒条件检查
waitStatus != 0
表示存在需要唤醒的后继- 常见状态:
SIGNAL(-1)
表示后继需要被唤醒
- 唤醒算法
- 从尾向前查找 解决并发入队时的竞争问题:
- 新节点入队时:
tail
CAS ->prev
设置 ->next
设置 - 保证
prev
链总是完整的,避免next
链临时断裂
- 新节点入队时:
- 状态清除机制
- 清除
SIGNAL
状态避免重复唤醒 - 使用 CAS 保证原子性操作
设计
-
级联唤醒避免
仅唤醒直接后继,避免不必要的线程唤醒风暴 -
取消节点跳过
自动过滤CANCELLED
节点(waitStatus>0
) -
双向遍历保障
从尾向前的遍历保证总能找到有效节点:for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; // 最后一个有效节点
-
无锁唤醒机制
LockSupport.unpark()
是轻量级精准唤醒操作:- 若线程未阻塞,设置"许可"标记
- 若线程已阻塞,立即解除阻塞状态
唤醒后的线程生命周期
注意:被唤醒的线程会在
acquireQueued
方法中继续尝试获取资源,成功后成为新的头节点并继续执行。
流程图
acquireShared(...)
方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 1. 尝试共享获取
doAcquireShared(arg); // 2. 获取失败则排队
}
// 子类实现的共享获取逻辑
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// 核心排队逻辑
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED); // 创建共享模式节点
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg); // 再次尝试获取
if (r >= 0) {
setHeadAndPropagate(node, r); // 关键:设置头节点并传播
p.next = null; // GC
if (interrupted)
selfInterrupt();
return;
}
}
// 阻塞控制(与独占模式相同)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
// 传播唤醒的核心方法
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // 记录旧头节点
setHead(node); // 设置新头节点
// 传播条件检查(四种情况)
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 (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue; // CAS失败重试
unparkSuccessor(h); // 唤醒后继
}
// 设置PROPAGATE状态保证传播继续
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue;
}
if (h == head) break; // 头节点变化则继续传播
}
}
说明
-
共享获取尝试
- 子类实现(如 Semaphore 返回
permits-1
) 返回值 >=0
表示获取成功
- 子类实现(如 Semaphore 返回
-
共享节点入队
- 创建
Node.SHARED
模式节点 - 入队逻辑与独占模式相同(CAS 添加到尾节点)
- 创建
-
传播机制(核心)
传播条件(任一满足即传播):
propagate > 0
(还有剩余资源)- 旧头节点状态
<0
(含 SIGNAL/PROPAGATE) - 新头节点状态
<0
- 头节点为 null(防御性检查)
- 级联唤醒(doReleaseShared)
- 通过循环保证唤醒信号持续传播
PROPAGATE(-3)
状态确保新请求加入时继续传播
与独占模式的区别
特性 | 共享模式 | 独占模式 |
---|---|---|
节点类型 | Node.SHARED | Node.EXCLUSIVE |
唤醒范围 | 级联唤醒多个线程 | 仅唤醒一个后继 |
状态传播 | PROPAGATE 状态机制 | 无 |
典型应用 | Semaphore, CountDownLatch | ReentrantLock |
设计
-
级联唤醒优化
共享模式成功获取后自动唤醒后续共享节点,提高吞吐量 -
PROPAGATE 状态解决竞争
解决极端场景下的信号丢失问题:// 场景:线程A释放资源 -> 线程B获取资源 -> 线程C同时请求资源 else if (ws == 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE)) continue; // 确保新请求不会丢失唤醒信号
-
无界资源传播
当资源充足时(如 Semaphore 许可数很大),一次成功获取可唤醒整个队列 -
与独占模式复用队列
共享节点和独占节点可在同一队列共存,通过nextWaiter
区分
执行示例(Semaphore)
注意:当共享节点被唤醒后,会通过
setHeadAndPropagate
继续唤醒后续共享节点,形成级联唤醒效果,这是共享模式高吞吐量的关键设计。
流程图
releaseShared(...)
方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 1. 尝试共享释放
doReleaseShared(); // 2. 执行共享唤醒
return true;
}
return false;
}
// 子类实现的共享释放逻辑
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
// 共享模式唤醒核心
private void doReleaseShared() {
for (;;) { // 自旋循环
Node h = head;
if (h != null && h != tail) { // 队列中有等待节点
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 需要唤醒后继
// CAS清除SIGNAL状态
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue; // CAS失败重试
unparkSuccessor(h); // 唤醒后继
}
// 设置PROPAGATE状态保证传播继续
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue; // CAS失败重试
}
// 检查头节点是否变化(有线程成为新头)
if (h == head) // 头节点未变化则退出
break;
}
}
说明
- 共享资源释放
- 子类负责实现状态更新逻辑
- 返回
true
表示释放成功
- 唤醒循环机制
- 循环直到头节点稳定不变
- 处理过程中可能有新线程成为头节点
- 状态处理
- SIGNAL(-1):立即唤醒后继节点
- 0状态:设为 PROPAGATE(-3) 确保后续传播
- PROPAGATE 状态
- 解决极端场景下的信号丢失问题
- 保证新请求不会错过唤醒信号
设计
-
级联传播保证
for (;;) { // ... if (h == head) break; // 关键退出条件 }
- 只要头节点变化(表示有新线程获取资源),就继续传播
- 确保释放信号穿透整个队列
-
无锁并发控制
- 所有状态更新使用 CAS 操作
compareAndSetWaitStatus
保证原子性
-
唤醒效率优化
- 与
acquireShared
的setHeadAndPropagate
协同工作 - 共享资源充足时一次性唤醒整个队列
- 与
-
状态机转换
与独占模式释放的对比
特性 | 共享模式 (releaseShared) | 独占模式 (release) |
---|---|---|
唤醒范围 | 可能唤醒多个后继节点 | 仅唤醒一个后继节点 |
核心方法 | doReleaseShared() | unparkSuccessor() |
状态变化 | 涉及 PROPAGATE 状态 | 仅 SIGNAL 状态 |
循环机制 | 自旋直到头节点稳定 | 单次执行 |
典型应用 | CountDownLatch, Semaphore | ReentrantLock |
关键:
releaseShared()
通过doReleaseShared()
的自旋循环和状态传播机制,确保共享资源的释放信号能在队列中持续传播,这是共享同步器(如 Semaphore、CountDownLatch)实现"一对多"唤醒的基础。PROPAGATE 状态的引入解决了并发场景下的信号丢失问题。
流程图
五、总结
特性 | 描述 |
---|---|
模板方法模式 | AQS 提供通用逻辑,子类通过重写钩子方法定制行为 |
双链表队列 | FIFO 队列保证公平性,支持并发插入 |
CAS + volatile | 实现无锁化队列操作与状态更新 |
线程阻塞机制 | 使用 LockSupport.park/unpark 控制线程挂起与唤醒 |
六、共享模式 vs 独占模式
类型 | 示例 | 支持多个线程同时获取 |
---|---|---|
独占模式 | ReentrantLock | 否 |
共享模式 | CountDownLatch、Semaphore | 是 |
- 独占模式:一次只能一个线程获取同步状态;
- 共享模式:允许多个线程同时获取同步状态。