知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方可以评论,我们一起探讨!
AbstractQueuedSynchronizer(AQS)深度分析
1. 概述
AQS 是 Java 并发包(java.util.concurrent.locks)的核心框架,用于构建锁和其他同步器。其核心思想是通过 同步状态(state) 和 CLH 队列变种 管理线程的排队与唤醒,支持 独占模式 和 共享模式。
2. 核心组件
(1) 同步状态(state)
- 作用:通过
volatile int变量表示资源状态,具体语义由子类定义。- 示例:
ReentrantLock:state=0表示未锁定,state>0表示重入次数。Semaphore:state表示可用许可数。
- 示例:
- 原子操作:通过
CAS(compareAndSetState)保证线程安全。
(2) CLH 队列(等待队列)
- 结构:双向链表(头节点为虚拟节点),节点类型为
Node。 - 节点模式:
- 独占模式(EXCLUSIVE):如
ReentrantLock,同一时刻仅一个线程可持有资源。 - 共享模式(SHARED):如
Semaphore,多个线程可同时获取资源。
- 独占模式(EXCLUSIVE):如
(3) 节点状态(waitStatus)
- CANCELLED (1):线程已取消等待(如超时或中断)。
- SIGNAL (-1):当前节点的后继节点需要被唤醒。
- CONDITION (-2):节点处于条件队列(
ConditionObject)。 - PROPAGATE (-3):共享模式下唤醒需传播到后续节点。
3. 核心方法
AQS 采用 模板方法模式,子类需实现以下方法定义同步逻辑:
- 独占模式:
tryAcquire(int arg):尝试获取资源。tryRelease(int arg):尝试释放资源。
- 共享模式:
tryAcquireShared(int arg):尝试共享式获取资源。tryReleaseShared(int arg):尝试共享式释放资源。
- 状态查询:
isHeldExclusively():资源是否被独占。
4. 独占模式流程
(1) 获取资源(acquire)
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取资源(子类实现)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并阻塞
selfInterrupt(); // 恢复中断状态
}
- 步骤:
- 调用
tryAcquire尝试获取资源,成功则直接返回。 - 失败则创建独占模式节点,通过
addWaiter加入队列尾部。 - 调用
acquireQueued自旋或阻塞,直到获取资源或被中断。
- 调用
(2) 释放资源(release)
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放资源(子类实现)
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继节点
return true;
}
return false;
}
- 步骤:
- 调用
tryRelease释放资源。 - 唤醒队列中第一个有效后继节点(
unparkSuccessor)。
- 调用
5. 共享模式流程
(1) 获取资源(acquireShared)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 尝试共享获取(返回剩余许可数)
doAcquireShared(arg); // 加入队列并等待
}
- 步骤:
- 调用
tryAcquireShared,返回值<0表示资源不足。 - 调用
doAcquireShared加入队列并自旋,直到成功获取或被中断。
- 调用
(2) 释放资源(releaseShared)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 尝试释放资源
doReleaseShared(); // 唤醒后续共享节点
return true;
}
return false;
}
- 步骤:
- 调用
tryReleaseShared释放资源。 - 调用
doReleaseShared唤醒后续节点,传播共享模式唤醒。
- 调用
6. 条件变量(ConditionObject)
- 作用:实现类似
Object.wait()/notify()的等待/通知机制,但更灵活。 - 结构:每个
ConditionObject维护一个单向条件队列。 - 关键方法:
await():释放锁,进入条件队列阻塞。signal():将条件队列中的节点转移到同步队列,等待获取锁。
示例:生产者-消费者模型
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
// 生产者
lock.lock();
try {
while (queue.isFull())
notFull.await(); // 进入条件队列等待
queue.put(item);
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
7. 关键设计思想
(1) 模板方法模式
- 分离关注点:AQS 处理队列管理与线程调度,子类定义资源获取/释放逻辑。
- 复用性:同一套队列机制支持多种同步器(如锁、信号量)。
(2) 无锁队列管理
- CAS 操作:通过
compareAndSetTail、compareAndSetHead等实现线程安全的节点入队/出队。
(3) 自旋与阻塞平衡
- 优化策略:线程在入队后先自旋尝试获取资源,失败后再调用
LockSupport.park()阻塞,减少上下文切换开销。
(4) 中断与超时处理
- 响应中断:在
acquireInterruptibly和doAcquireNanos中处理中断信号和超时逻辑。
8. 性能优化点
- 头节点优化:头节点为虚拟节点,减少队列操作的竞争。
- 状态传播:共享模式下唤醒传播(
PROPAGATE状态),提高并发吞吐量。 - 取消节点清理:跳过已取消的节点(
waitStatus=CANCELLED),避免无效遍历。
9. 对比 Synchronized
| 特性 | AQS | Synchronized |
|---|---|---|
| 实现方式 | 基于 CAS 和 CLH 队列 | 基于 Monitor(JVM 内置锁) |
| 灵活性 | 高(支持自定义同步策略) | 低(仅支持内置锁) |
| 可中断性 | 支持 | 不支持 |
| 公平性 | 支持公平和非公平模式 | 仅非公平 |
| 条件变量 | 支持多个条件队列 | 单个条件队列 |
10. 典型应用
- ReentrantLock:可重入独占锁,支持公平/非公平模式。
- Semaphore:控制并发线程数的信号量。
- CountDownLatch:等待多个任务完成的同步器。
- ReentrantReadWriteLock:读写锁,分离读/写操作。
11. 源码关键片段解析
(1) 节点入队(addWaiter)
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) { // CAS 设置尾节点
pred.next = node;
return node;
}
}
enq(node); // 队列为空或 CAS 失败时,自旋入队
return node;
}
(2) 自旋获取资源(acquireQueued)
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)) { // 前驱是头节点且获取成功
setHead(node);
p.next = null; // 帮助 GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 检查是否需要阻塞
parkAndCheckInterrupt()) // 阻塞并检查中断
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // 取消获取
}
}
12. 总结
AQS 通过 同步状态管理 和 CLH 队列 实现了高效的线程同步机制,其设计体现了以下核心思想:
- 模板方法模式:分离通用逻辑与具体实现。
- 无锁化操作:通过 CAS 减少锁竞争。
- 灵活扩展性:支持独占、共享模式及条件变量。
- 性能优化:自旋尝试、头节点优化、状态传播等。