AbstractQueuedSynchronizer
(简称 AQS)是 Java 并发包(java.util.concurrent
)中最核心的基础类之一,几乎所有自定义的同步器(如ReentrantLock
、CountDownLatch
、Semaphore
、FutureTask
等)都直接或间接地基于 AQS 实现。本文将深入探讨 AQS 的核心设计思想、内部数据结构、关键方法实现、以及它背后的并发控制模型。
🧠 一、AQS 的核心设计思想
AQS 提供了一个 队列式的、基于状态的同步框架,它的设计思想非常明确:
🌟 基本模型:
通过维护一个 volatile 的 state 变量来表示同步状态,并用一个 FIFO 队列管理等待线程。
具体来说,它实现了两种锁的语义:
- 独占(Exclusive) :只能一个线程获取,比如
ReentrantLock
。 - 共享(Shared) :多个线程可以同时获取,比如
Semaphore
。
开发者通过继承 AQS 并重写其 tryAcquire
/ tryRelease
等方法来实现自定义的同步逻辑,而不需要关心线程挂起/唤醒的底层细节。
🏗️ 二、AQS 的关键内部结构
1. state
变量(同步状态)
java
private volatile int state;
这是 AQS 的核心状态变量,通常用来表示资源是否可用、计数器大小等。子类通过 getState()
/ setState()
/ compareAndSetState()
来访问和修改它。
2. CLH 队列(等待队列)
AQS 使用一种变种的 CLH(Craig, Landin, and Hagersten)锁队列 来维护所有正在等待获取锁的线程。
java
static final class Node {
volatile Node prev;
volatile Node next;
volatile Thread thread;
volatile int waitStatus;
}
常见的 waitStatus
值:
0
:默认状态SIGNAL (-1)
:后继节点等待唤醒CANCELLED (1)
:线程取消等待CONDITION (-2)
:表示在 condition 上等待PROPAGATE (-3)
:共享模式传播
队列头尾指针:
java
private transient volatile Node head;
private transient volatile Node tail;
AQS 通过 enq()
和 acquireQueued()
等方法管理队列入队/出队与线程阻塞/唤醒的逻辑。
⚙️ 三、核心方法详解
1. acquire(int arg)
:独占模式获取
java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
流程如下:
- 首先尝试获取锁(调用子类实现的
tryAcquire()
); - 获取失败则将当前线程包装成 Node 并加入等待队列;
- 在队列中自旋等待,并最终阻塞(
LockSupport.park()
); - 直到被前驱唤醒并重新竞争资源。
2. release(int arg)
:独占模式释放
java
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()
释放资源; - 如果成功,唤醒等待队列中的下一个线程。
3. acquireShared(int arg)
/ releaseShared(int arg)
共享模式下的获取和释放,允许多个线程同时访问资源(如信号量)。
java
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
🎯 四、自定义同步器的三大步骤
要实现一个自定义同步器,一般遵循以下步骤:
1. 继承 AbstractQueuedSynchronizer
java
class MyLock extends AbstractQueuedSynchronizer {
...
}
2. 实现同步逻辑(重写关键方法)
java
protected boolean tryAcquire(int arg) {
// 例如实现不可重入独占锁
return compareAndSetState(0, 1);
}
protected boolean tryRelease(int arg) {
setState(0);
return true;
}
3. 暴露锁 API 给外部调用
java
public void lock() {
acquire(1);
}
public void unlock() {
release(1);
}
💡 五、AQS 的典型应用场景
类名 | 功能 | AQS 模式 |
---|---|---|
ReentrantLock |
可重入独占锁 | 独占 |
Semaphore |
信号量 | 共享 |
CountDownLatch |
倒计时器 | 共享 |
ReadWriteLock |
读写锁 | 独占 + 共享 |
FutureTask |
任务状态管理 | 独占 |
这些类都封装了 AQS 提供的同步能力,让我们得以用更高层的 API 进行线程协作。
🔍 六、深入理解:CLH 队列与线程挂起/唤醒
✴️ 挂起线程:LockSupport.park()
当线程获取锁失败,就会调用 LockSupport.park(this)
挂起自己。
✴️ 唤醒线程:LockSupport.unpark(thread)
释放锁后,会唤醒队列中的下一个节点对应的线程。
这是一种 显式挂起-唤醒机制 ,相比传统 wait/notify
更加灵活和安全,不依赖对象 monitor。
🧪 七、AQS 存在的问题和挑战
- 非公平锁可能导致线程"饿死";
- 锁竞争激烈时,自旋+挂起开销大;
- 调试困难,出错后难以定位问题源头;
- 死锁风险高 ,一旦
tryAcquire
实现不当容易出问题;
因此,在使用 AQS 时,强烈建议封装为高层 API 使用,不要轻易手写同步器,除非非常熟悉。
✅ 总结
特性 | 描述 |
---|---|
模型 | 基于状态的同步器 |
队列 | CLH 队列实现等待线程排队 |
支持 | 独占与共享两种模式 |
控制点 | 子类重写 tryAcquire / tryRelease 等控制同步行为 |
应用 | ReentrantLock、Semaphore、CountDownLatch、FutureTask 等 |
📘 推荐阅读与资料
- 《Java 并发编程实战》(Java Concurrency in Practice)
- Doug Lea 的 AQS 原始设计文档
- JDK 源码中的
AbstractQueuedSynchronizer.java
注释 - 博客:Martin Fowler 的并发模式分析