Java并发编程基石:AQS(AbstractQueuedSynchronizer)深度解析
一、AQS在并发体系中的核心地位
1. JDK并发工具依赖关系
graph TD
A[AQS] --> B[ReentrantLock]
A --> C[CountDownLatch]
A --> D[Semaphore]
A --> E[ThreadPoolExecutor.Worker]
A --> F[ReentrantReadWriteLock]
2. AQS核心设计思想
设计理念 | 实现方式 | 优势 |
---|---|---|
模板方法模式 | 提供tryAcquire/tryRelease等钩子方法 | 支持自定义同步器 |
CLH队列变种 | 通过Node节点实现线程排队 | 公平性保障+低竞争开销 |
状态机管理 | volatile int state+CAS操作 | 高效的状态变更 |
二、AQS核心数据结构解析
1. 关键字段源码
// 同步状态(子类通过CAS修改)
private volatile int state;
// CLH队列头尾指针
private transient volatile Node head;
private transient volatile Node tail;
// Node节点结构
static final class Node {
volatile int waitStatus; // 取值:CANCELLED/SIGNAL/CONDITION/PROPAGATE
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter; // 条件队列专用
}
2. 状态流转图
stateDiagram
[*] --> INIT
INIT --> CANCELLED: 线程中断/超时
INIT --> SIGNAL: 前驱节点设置
INIT --> CONDITION: 进入条件队列
SIGNAL --> BLOCKED: 进入阻塞状态
BLOCKED --> ACQUIRED: 获取锁成功
三、独占模式源码剖析(以ReentrantLock为例)
1. 获取锁流程
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 子类实现
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node)) // 检查是否需要阻塞
interrupted |= parkAndCheckInterrupt(); // 调用LockSupport.park
}
} catch (Throwable t) {
cancelAcquire(node); // 异常处理
throw t;
}
}
2. 释放锁流程
public final boolean release(int arg) {
if (tryRelease(arg)) { // 子类实现
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继节点
return true;
}
return false;
}
四、共享模式实现(以CountDownLatch为例)
1. 关键差异点
模式 | 共享标识 | 传播机制 | 典型应用 |
---|---|---|---|
独占模式 | Node.EXCLUSIVE | 不传播 | ReentrantLock |
共享模式 | Node.SHARED | 通过doReleaseShared传播 | CountDownLatch/Semaphore |
2. 共享锁获取示例
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 返回值<0表示获取失败
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r); // 关键:设置头节点并传播
return;
}
}
if (shouldParkAfterFailedAcquire(p, node))
parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
五、条件队列实现原理
1. await/signal流程
// await() 核心逻辑
public final void await() throws InterruptedException {
Node node = addConditionWaiter(); // 加入条件队列
int savedState = fullyRelease(node); // 完全释放锁
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 阻塞
if (Thread.interrupted())
throw new InterruptedException();
}
acquireQueued(node, savedState); // 重新竞争锁
}
// signal() 核心逻辑
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && // 转移到同步队列
(first = firstWaiter) != null);
}
2. 与synchronized条件队列对比
维度 | AQS条件队列 | synchronized wait/notify |
---|---|---|
队列数量 | 支持多个条件队列 | 单个等待队列 |
灵活性 | 可中断/超时等待 | 仅基础等待/通知 |
性能 | 更细粒度的控制 | JVM内置优化 |
六、AQS性能优化实践
1. 避免CLH队列竞争技巧
- 快速路径尝试:先直接CAS获取状态(如ReentrantLock的非公平模式)
- 延迟初始化:只有竞争时才构建队列(head/tail的懒加载)
2. 状态设计建议
// 正确示例:读写锁状态拆分
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 读锁占用高16位,写锁占用低16位
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
七、常见问题QA
💬 Q1:为什么AQS采用CLH队列而非MCS队列?
✅ 答案:
- 前驱节点监听:CLH通过前驱节点的waitStatus减少自旋
- 无锁入队:CAS操作只需修改tail指针
- 内存效率:节点只需保存后继指针(MCS需保存前驱指针)
💬 Q2:如何实现一个自定义同步器?
✅ 步骤示例(实现不可重入锁):
class Mutex extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) { // 仅当state=0时获取
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0); // 不需要CAS,因为只有持有线程能释放
return true;
}
}
💬 Q3:AQS如何处理线程中断?
✅ 中断策略:
- 抢锁阶段:仅设置中断标记(acquireQueued返回true)
- 条件等待:立即抛出InterruptedException
- 取消节点:将waitStatus设为CANCELLED并移除
最佳实践:
- 优先使用现有同步工具(如ReentrantLock)
- 自定义同步器时严格遵循模板方法规范
- 监控队列长度(可通过getQueuedThreads()诊断竞争情况)
通过
jstack
可查看AQS队列中的等待线程