请解释AQS在 Java 并发编程中的实现原理。
参考回答
AQS(AbstractQueuedSynchronizer) 是 Java 并发包中的一个重要基础类,位于 java.util.concurrent.locks
包下。它是用来构建同步工具(如锁、信号量、CountDownLatch 等)的框架,为实现线程的排队、状态管理以及线程同步提供了统一的低级支持。
AQS 的核心原理:
- 状态(state):使用一个整型变量
state
表示同步状态(如锁是否被占用、信号量的剩余许可等)。 - 队列(CLH 队列):使用一个双向队列存储等待获取同步状态的线程,线程以节点(
Node
)形式入队。 - 模板方法:通过
acquire
和release
方法管理线程的获取和释放,具体逻辑由子类实现。
详细讲解与拓展
1. AQS 的核心组成部分
- 同步状态(state):
- 是一个
volatile
的整型变量,表示共享资源的状态。 - 子类通过重写
tryAcquire
、tryRelease
等方法,自定义对状态的操作。 - 例如:
- 在独占锁中,
state
为 1 表示锁已被占用,0 表示未被占用。 - 在共享锁中,
state
表示剩余许可的数量。
- 在独占锁中,
- 是一个
- 等待队列(CLH 队列):
- AQS 使用一个基于 CLH 队列 的双向链表来保存等待锁的线程。
- 每个线程被封装成一个
Node
对象,并通过prev
和next
指针形成双向链表。 - 线程通过
CAS
操作安全地入队和出队。
- Node 类:
- 每个节点表示一个线程,其状态包括:
CANCELLED
:线程已取消。SIGNAL
:线程需要被唤醒。CONDITION
:线程正在等待条件。PROPAGATE
:线程的共享模式需要传播。
- 每个节点表示一个线程,其状态包括:
- 模板方法:
- AQS 提供了模板方法(如
acquire
、release
),具体的同步逻辑由子类实现。 - 子类需要实现:
- 独占模式:
tryAcquire
、tryRelease
。 - 共享模式:
tryAcquireShared
、tryReleaseShared
。
- 独占模式:
- AQS 提供了模板方法(如
2. AQS 的工作流程
AQS 的核心是对线程的同步状态管理,包括获取和释放同步状态。以下以独占模式为例,展示 AQS 的工作流程。
2.1 获取锁(acquire
方法)
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 调用子类实现的
tryAcquire
尝试获取锁:- 如果成功,线程直接进入临界区。
- 如果失败,将线程封装成一个
Node
并加入等待队列。
- 在等待队列中,线程通过
acquireQueued
方法自旋等待:- 当前节点检查前驱节点是否释放锁(
SIGNAL
状态)。 - 如果未释放,线程进入阻塞状态,等待被唤醒。
- 当前节点检查前驱节点是否释放锁(
- 线程被唤醒后,再次尝试获取锁。
2.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
方法):- 将等待线程从阻塞状态变为可运行状态。
3. AQS 的实现模式
AQS 支持两种同步模式:
- 独占模式(Exclusive):
- 每次只有一个线程能够获取同步状态。
- 典型实现:
ReentrantLock
。
- 共享模式(Shared):
- 多个线程可以共享同步状态。
- 典型实现:
Semaphore
、CountDownLatch
。
- 典型实现:
示例:ReentrantLock
的独占模式
protected final boolean tryAcquire(int arg) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 未被占用
if (compareAndSetState(0, arg)) { // CAS 修改 state
setExclusiveOwnerThread(current); // 设置当前线程为持有者
return true;
}
} else if (current == getExclusiveOwnerThread()) { // 重入
setState(c + arg);
return true;
}
return false;
}
示例:Semaphore
的共享模式
protected final int tryAcquireShared(int arg) {
for (;;) {
int available = getState();
int remaining = available - arg;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
4. AQS 的扩展
- 常见实现类
ReentrantLock
:独占模式,基于 AQS 管理锁的获取和释放。Semaphore
:共享模式,用于限制资源访问的线程数。CountDownLatch
:共享模式,通过计数器控制线程的执行顺序。CyclicBarrier
:基于Condition
实现线程的同步屏障。
- 自定义同步器
- 通过继承 AQS 并重写
tryAcquire
、tryRelease
等方法,可以实现自定义同步工具。
- 通过继承 AQS 并重写
示例:简单独占锁
class SimpleLock extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1); // 0 表示未占用,1 表示占用
}
@Override
protected boolean tryRelease(int arg) {
setState(0); // 释放锁
return true;
}
public void lock() {
acquire(1); // 获取锁
}
public void unlock() {
release(1); // 释放锁
}
}
5. AQS 的优缺点
优点:
- 高度可扩展:
- 通过重写模板方法,可以方便地实现各种同步工具。
- 高性能:
- 基于 CAS 操作和 CLH 队列实现,无需传统的线程上下文切换。
- 线程安全:
- 使用
volatile
和原子操作保证线程安全。
- 使用
缺点:
- 实现复杂:
- AQS 的源码较复杂,新手不易理解。
- 锁的竞争激烈时性能下降:
- 阻塞线程需要挂起和唤醒,开销较大。
总结
- AQS 是并发包的核心基础: 它通过管理同步状态和队列,为锁、信号量等工具提供了强大的支持。
- 核心原理: 使用
state
表示同步状态,使用 CLH 队列管理线程的等待顺序。 - 适用场景: 需要自定义复杂同步逻辑的场景,例如实现锁、信号量、栅栏等工具。