AQS 是 Java 并发包 (java.util.concurrent.locks) 的核心基础框架,用于构建锁和同步器(如 ReentrantLock、Semaphore、CountDownLatch 等)。其核心思想是通过 共享状态(state) 和 队列管理 实现线程的阻塞与唤醒。
一、AQS 核心组件
-
共享状态(state)
-
通过
volatile int state表示资源状态,具体含义由子类定义。 -
例如:
ReentrantLock:state=0表示未锁定,state>0表示锁定次数(可重入)。Semaphore:state表示可用许可数量。
-
-
FIFO 等待队列(CLH 变体)
-
使用双向链表(
Node类)管理等待线程。 -
节点模式:
Node.EXCLUSIVE:独占模式节点(如ReentrantLock)。Node.SHARED:共享模式节点(如Semaphore)。
-
二、AQS 的两种资源共享模式
-
独占模式(Exclusive)
-
资源只能被一个线程持有(如
ReentrantLock)。 -
关键方法:
protected boolean tryAcquire(int arg) // 尝试获取资源(需子类实现) protected boolean tryRelease(int arg) // 尝试释放资源
-
-
共享模式(Shared)
-
资源可被多个线程共享(如
Semaphore、CountDownLatch)。 -
关键方法:
protected int tryAcquireShared(int arg) // 返回剩余可用资源数 protected boolean tryReleaseShared(int arg)
-
三、AQS 的工作流程
独占模式
-
获取资源(如
lock())- 调用
tryAcquire尝试直接获取资源。 - 若失败,将线程封装为
Node加入队列尾部,并自旋或阻塞(通过LockSupport.park())。 - 前驱节点释放资源时,唤醒后继节点。
- 调用
-
释放资源(如
unlock())- 调用
tryRelease释放资源。 - 唤醒队列中的第一个有效节点(
unparkSuccessor)。
- 调用
共享模式
1.获取资源(acquireShared)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 尝试获取资源
doAcquireShared(arg); // 失败则加入队列并阻塞
}
-
步骤解析:
- 尝试获取资源:调用
tryAcquireShared(arg),若返回值≥0表示成功,直接访问资源。 - 入队阻塞:若失败(返回值
<0),将线程封装为共享模式节点(Node.SHARED),加入队列尾部。 - 自旋检查:在队列中自旋检查前驱节点是否为头节点,并尝试再次获取资源。
- 阻塞线程:若仍失败,调用
LockSupport.park()阻塞线程。
- 尝试获取资源:调用
2. 资源传播与唤醒
-
当资源被释放时,AQS 会唤醒队列中的后续节点,并 传播可用资源信号:
- 若后续节点是共享模式,唤醒后会继续尝试获取资源,成功后继续唤醒其后继节点。
- 这种机制确保多个共享线程可以同时获取资源(如
Semaphore允许多个线程获取许可)。
3. 释放资源(releaseShared)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 尝试释放资源
doReleaseShared(); // 唤醒后续节点并传播
return true;
}
return false;
}
-
步骤解析:
- 释放资源:调用
tryReleaseShared(arg)更新state。 - 唤醒传播:调用
doReleaseShared(),唤醒队列中的第一个有效节点,并触发资源传播逻辑。
- 释放资源:调用
四、AQS 源码关键逻辑
-
Node 类
static final class Node { volatile int waitStatus; // 状态:CANCELLED(1)、SIGNAL(-1)、CONDITION(-2) volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 volatile Thread thread; // 关联的线程 Node nextWaiter; // 条件队列中的下一个节点 } -
获取资源的核心方法
acquirepublic final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); // 恢复中断状态 } private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); // 创建共享节点并入队 boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 获取前驱节点 if (p == head) { // 前驱是头节点时尝试获取资源 int r = tryAcquireShared(arg); if (r >= 0) { // 获取成功 setHeadAndPropagate(node, r); // 更新头节点并传播 if (interrupted) selfInterrupt(); failed = false; return; } } // 检查是否需要阻塞(前驱状态为 SIGNAL) if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 阻塞线程 interrupted = true; } } finally { if (failed) cancelAcquire(node); // 取消获取 } } -
释放资源的核心方法
releasepublic final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); // 唤醒后继节点 return true; } return false; } 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)) { unparkSuccessor(h); // 唤醒后继节点 } } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) { continue; // 确保传播状态 } } if (h == head) // 头节点未变化则退出循环 break; } }
五、基于 AQS 实现自定义同步器示例
以下是一个 非可重入的互斥锁 实现:
public class SimpleMutex extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) { // CAS 设置 state 为 1
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0); // 直接设置 state 为 0(无需 CAS)
return true;
}
public void lock() {
acquire(1);
}
public void unlock() {
release(1);
}
}
六、AQS 的高级特性
-
条件变量(Condition)
-
通过
ConditionObject实现,内部维护一个条件队列。 -
使用示例:
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { condition.await(); // 释放锁并进入条件队列 condition.signal(); // 唤醒条件队列中的一个线程 } finally { lock.unlock(); }
-
-
公平锁与非公平锁
- 公平锁:严格按照队列顺序获取资源。
- 非公平锁:允许插队(通过直接 CAS 尝试获取资源)。
-
可重入性
ReentrantLock通过记录当前持有线程和重入次数实现。
七、常见问题与优化
-
性能问题
- 自旋优化:在进入阻塞前短暂自旋,减少上下文切换。
- 队列头节点唤醒:避免不必要的唤醒操作。
-
中断处理
- AQS 在
acquire方法中处理中断,通过Thread.interrupted()检查中断状态。
- AQS 在
-
避免死锁
- 使用超时机制(如
tryLock(long timeout, TimeUnit unit))。
- 使用超时机制(如
八、总结
| 特性 | 描述 |
|---|---|
| 核心思想 | 通过共享状态(state)和队列管理实现线程同步。 |
| 关键组件 | state、CLH 队列、Node 节点。 |
| 资源共享模式 | 独占模式(如锁)、共享模式(如信号量)。 |
| 应用场景 | 构建高性能锁、同步工具(如 ReentrantLock、Semaphore)。 |
| 优点 | 灵活性高,开发者可基于 AQS 快速实现自定义同步器。 |
通过深入理解 AQS,可以更好地掌握 Java 并发包的底层机制,并设计出高效的线程同步方案。