Appearance
AQS 共享模式源码深度解析
一、AQS 核心架构与同步机制
1.1 核心成员变量
java
// 同步状态变量(volatile保证可见性)
private volatile int state;
// CLH队列头尾节点
private transient volatile Node head;
private transient volatile Node tail;
1.2 节点状态定义
java
static final class Node {
// 等待状态常量及其核心含义:
// CANCELLED(1):节点取消,可能由于超时或中断
// SIGNAL(-1):需要唤醒后继节点,当前节点释放锁后必须触发唤醒
// CONDITION(-2):节点在条件队列中等待,与Condition实现相关
// PROPAGATE(-3):共享传播状态,用于保证读写可见性及传播唤醒
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// 当前节点的等待状态(通过CAS修改)
volatile int waitStatus;
// CLH队列中的前驱和后继节点,形成双向链表结构
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
// 关联的线程对象,表示该节点是由哪个线程创建的
volatile Thread thread; // 关联线程
}
二、共享模式核心流程解析
2.1 获取共享锁(acquireShared)
java
public final void acquireShared(int arg) {
// 调用 tryAcquireShared 尝试获取共享锁,返回值表示获取结果:
// - 如果返回值 >= 0,表示成功获取共享锁,无需进入队列;
// - 如果返回值 < 0,表示获取失败,需要进入 CLH 队列等待。
if (tryAcquireShared(arg) < 0) {
// 进入 CLH 队列,通过自旋和线程挂起的方式等待锁的释放。
// doAcquireShared 方法会将当前线程封装为共享模式节点,并加入同步队列,
// 同时在适当条件下尝试重新获取锁或传播唤醒信号。
doAcquireShared(arg);
}
}
2.1.1 自旋获取实现(深度剖析)
java
/**
* 共享模式下获取资源的核心实现:
* 1. 创建共享节点并入队
* 2. 自旋检查前驱节点状态
* 3. 尝试获取资源并处理传播逻辑
*/
private void doAcquireShared(int arg) {
// 创建共享模式节点并入队(SHARED标记区别于独占模式)
final Node node = addWaiter(Node.SHARED);
boolean failed = true; // 获取资源失败标记
try {
// 自旋获取资源(典型CAS失败重试模式)
for (;;) {
final Node p = node.predecessor(); // 获取前驱节点
// 关键路径:当前驱为头节点时尝试获取资源
if (p == head) {
int r = tryAcquireShared(arg); // 模板方法,子类实现
// 获取成功后的处理流程
if (r >= 0) {
setHead(node); // 新头节点设置(包含thread清空操作)
p.next = null; // 断开原头节点链接帮助GC
// 资源剩余量>0时触发传播唤醒(共享模式核心)
if (r > 0) {
doReleaseShared(); // 唤醒后继共享节点
}
failed = false;
break;
}
}
// 线程挂起逻辑(实际包含shouldParkAfterFailedAcquire检查)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) {
// 响应中断但不抛出异常(AQS标准处理方式)
break;
}
}
} finally {
// 异常处理:取消获取并清理节点状态
if (failed) {
cancelAcquire(node); // 处理CANCELLED状态
}
}
}
2.2 释放共享锁(releaseShared)
java
/**
* 共享模式下释放同步状态的标准入口
* 1. 调用 tryReleaseShared 尝试原子释放资源(子类实现)
* 2. 成功后通过 doReleaseShared 触发传播唤醒机制
*
* @param arg 释放参数,具体语义由子类定义
* @return 是否成功释放资源
*/
public final boolean releaseShared(int arg) {
// 模板方法设计模式:
// 1. 父类定义框架(final防止重写)
// 2. 子类实现具体逻辑(tryReleaseShared)
if (tryReleaseShared(arg)) {
// 唤醒传播机制核心:
// - 可能唤醒多个共享节点
// - 确保剩余资源量正确传递
// - 使用CAS避免并发竞争
doReleaseShared();
return true;
}
return false;
}
2.2.1 唤醒传播机制(深度解析版)
java
private void doReleaseShared() {
for (;;) {
Node h = head; // 获取当前头节点(volatile读保证可见性)
// 非空队列且存在后继节点需要处理:
// 1. h != null:确保队列已初始化
// 2. h != tail:说明有至少一个等待节点
if (h != null && h != tail) {
int ws = h.waitStatus; // 获取当前头节点状态(volatile读)
// SIGNAL状态表示后续节点需要唤醒:
// - 当前节点释放后必须触发唤醒
// - CAS修改状态避免并发竞争
if (ws == Node.SIGNAL) {
// 尝试将等待状态置为0(无信号):
// compareAndSetWaitStatus是原子操作,失败则重试整个循环
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
continue; // CAS失败则重试整个循环(典型CAS失败重试模式)
}
// 唤醒后继节点(底层调用LockSupport.unpark):
// 包含异常安全处理,确保线程继续执行
unparkSuccessor(h);
}
// PROPAGATE状态确保唤醒传播继续:
// - 0 → PROPAGATE 确保后续获取能继续传播
// - 这是共享模式的核心设计之一
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
continue; // 状态修改失败时重新迭代(保持自旋一致性)
}
}
// 如果头节点未改变,说明传播已完成:
// 1. h == head:没有新的节点成为头节点
// 2. break退出循环完成唤醒流程
// 否则继续循环以处理新的头节点(如新获取成功的节点)
if (h == head) break;
}
}
三、典型应用场景分析
3.1 CountDownLatch 实现原理(深度剖析版)
java
/**
* 共享模式下尝试获取同步状态的核心实现:
* - 返回值表示获取结果:
* - >=0:成功获取,可退出自旋并继续执行后续逻辑
* - <0:失败获取,需进入或继续等待
*/
protected int tryAcquireShared(int acquires) {
// 关键设计点:仅当同步状态(state)为0时允许获取
// 这体现了CountDownLatch的"门闩"特性:
// - 计数器未归零前任何线程都无法通过
// - 归零后所有等待线程被唤醒
return (getState() == 0) ? 1 : -1;
}
/**
* 原子化释放同步状态的核心实现:
* - 使用CAS自旋确保并发安全
* - 实现了"减到0时触发唤醒传播"的设计语义
*/
protected boolean tryReleaseShared(int releases) {
for (;;) { // CAS自旋标准写法
int c = getState(); // volatile读,保证可见性
// 边界处理:若当前状态已是0,直接返回false
if (c == 0) return false;
// 状态递减操作(非阻塞式原子更新)
int nextc = c - 1;
// CAS更新state字段:
// 1. compareAndSetState是Unsafe提供的原子操作
// 2. 失败则继续自旋重试
if (compareAndSetState(c, nextc)) {
// 仅当最终减到0时返回true以触发唤醒传播:
// - AQS框架约定:返回true将调用doReleaseShared
// - 这是共享模式唤醒机制的关键触发点
return nextc == 0;
}
}
}
3.2 Semaphore 共享资源控制(深度剖析实现)
java
/**
* 共享模式下尝试获取信号量资源的核心方法:
* - 返回值表示获取结果:
* - >=0:成功获取资源,remaining为剩余资源数
* - <0:资源不足,需进入CLH队列等待唤醒
*/
protected int tryAcquireShared(int acquires) {
for (;;) { // CAS自旋标准写法(无锁化设计核心)
int available = getState(); // volatile读保证可见性
// 资源计算:
// 1. available:当前可用资源数量
// 2. acquires:请求获取的数量
// 3. remaining:剩余资源数量
int remaining = available - acquires;
// 返回值处理逻辑:
// 1. remaining < 0:资源不足,返回负数触发排队等待
// 2. compareAndSetState成功:原子更新资源状态,返回剩余量
if (remaining < 0 || compareAndSetState(available, remaining)) {
return remaining; // 返回剩余资源数(共享模式关键设计点)
}
// CAS失败则继续自旋重试(典型无锁编程模式)
}
}
/**
* 原子化释放信号量资源的核心实现:
* - 使用CAS自旋确保并发安全
* - 返回true将触发doReleaseShared唤醒传播机制
*/
protected boolean tryReleaseShared(int releases) {
for (;;) { // 自旋重试保证原子性
int current = getState(); // volatile读取当前状态
// 资源增加操作:
// 1. current:当前可用资源数量
// 2. releases:要释放的资源数
// 3. next:新资源总量
int next = current + releases;
// 状态一致性保障:
// 1. CAS确保状态变更的原子性
// 2. 失败则重新自旋重试
if (compareAndSetState(current, next)) {
// 成功释放后总是返回true,这会触发:
// 1. doReleaseShared 唤醒后继节点
// 2. 实现资源释放后的传播唤醒机制
return true;
}
// CAS失败则继续自旋(保持无锁化设计优势)
}
}
四、设计精髓总结
- 状态抽象:通过
state
字段的灵活语义支持不同同步场景 - 无锁化设计:CAS+自旋减少线程阻塞
- 传播唤醒机制:避免单次唤醒导致的性能瓶颈
- 双端队列管理:高效维护等待线程顺序
- 模板方法模式:子类仅需实现核心逻辑