一文彻底搞懂 AQS 的实现原理与设计思想
一文彻底搞懂 AQS 的实现原理与设计思想
在面试中,AQS(AbstractQueuedSynchronizer)是一个常见的高频问题。面试官一问“你了解 AQS 吗”,你脑海里只蹦出几个词:“CAS”、“state”、“等待队列”,再深一点就答不上来了。如果让你亲自设计一个并发工具类,你可能连思路都没有。
别急,今天我们就从“如果你要自己实现一个并发工具类”这个角度出发,一步步拆解 AQS 的设计逻辑,并理解为什么它要这么做,为什么这么设计。
什么是并发工具类?实现一个并发工具类要解决什么?
所谓并发工具类,就是多个线程能安全访问共享资源的工具。
你得考虑几个核心问题:
- 如何判断共享资源是否正在被其他线程访问?
- 如果资源已被占用,新来的线程该怎么办?等?让?走?
- 如何管理多个等待线程?
- 如何在资源释放后有序地唤醒它们?
一、用 state 表示资源占用状态
最基础的思路就是:用一个变量 state 表示资源状态。
state = 0:资源空闲state = 1:资源已被某个线程占用
那你可能会问:为啥不用 boolean 呢?
那是因为有些资源支持多个线程同时访问(比如信号量 Semaphor),也有些场景支持同一线程重复加锁(比如 ReentrantLock 可重入锁)。这时候 state 就不能只表示两种状态了,必须用 int 类型。
二、保证可见性与原子性:volatile + CAS
既然是多线程访问,那修改 state 时必须是线程安全的。
- 可见性:加上
volatile,保证一个线程改了,其他线程立刻能看见。 - 原子性:使用 CAS(Compare-And-Swap) 操作来原子修改
state。
线程尝试通过 CAS 修改 state,如果成功,就表示成功获得了锁或资源;如果失败了,说明资源已被其他线程占用。
三、CAS 失败怎么办?阻塞 + 等待队列
你不能无限 CAS 啊,一直自旋会浪费 CPU。
怎么办?失败就阻塞线程,并将它加入一个等待队列,等资源释放后唤醒它重新尝试。
为了管理这些线程,AQS 设计了一个基于 双向链表 的等待队列:
- 每个等待线程被封装为一个 Node 节点
- 入队时挂到尾部,唤醒时从头部依次唤醒
先进先出(FIFO)队列的设计正好可以实现公平策略。
四、再抽象一层:开放 tryAcquire() 给子类重写
AQS 并不直接定义怎么“获取资源”,它将这个动作留给子类去实现。
1 | protected boolean tryAcquire(int arg) { |
这就是模板方法设计模式的体现,AQS 只关心流程控制,不关心获取资源的细节。
你实现自己的并发工具类时,只需要重写 tryAcquire,即可定义加锁逻辑。
五、acquire vs tryAcquire:阻塞式与非阻塞式
AQS 定义了两种资源获取方式:
tryAcquire():尝试获取,失败就返回 false,不阻塞acquire():必须获取,失败就进队阻塞,直到成功
这是为上层业务场景考虑:
- 有些业务能容忍失败(例如尝试锁)
- 有些业务必须获取锁才能继续
六、公平锁与非公平锁
AQS 通过队列的 FIFO 特性,提供了公平锁的基础能力。
- 公平锁:先来的线程先获取资源(先进先出)
- 非公平锁:允许后来线程插队,跳过队列直接抢锁
例如:
1 | ReentrantLock fairLock = new ReentrantLock(true); |
这背后的实现,就是重写了 AQS 的 tryAcquire() 方法,是否检查前驱节点是否存在。
七、共享模式与独占模式
AQS 支持两种模式:
| 模式 | 说明 | 应用示例 |
|---|---|---|
| 独占 | 同一时间只能一个线程访问资源 | ReentrantLock |
| 共享 | 允许多个线程同时访问资源 | Semaphore、CountDownLatch |
独占模式下:
state表示重入次数- 加锁时 CAS 将 state 从 0 改为 1
- 解锁时减 state,减到 0 就完全释放
共享模式下:
state表示许可数量(如 Semaphore 初始为 3)- 获取时减 state,放行多个线程
- 释放时加 state
八、Condition 的实现:条件队列
使用 ReentrantLock 时经常会看到 Condition:
1 | Condition condition = lock.newCondition(); |
这是 AQS 的另一部分:条件队列。
- 没拿到锁的线程:放入 AQS 的 等待队列
- 拿到锁但资源未就绪、主动放弃执行的线程:放入
Condition的 条件队列
调用 await() 就会进入条件队列,等待 signal() 被唤醒,再进入等待队列重新竞争锁。
这比 synchronized 更灵活:你可以定义多个 Condition,对应多个等待条件和通知策略。
九、为什么不用操作系统的 Mutex?
AQS 不直接用操作系统的 Mutex 锁,而是自己设计了状态变量、CAS、自定义队列,目的就是性能更高、灵活性更强。
原因:
- Mutex 涉及系统调用(用户态 -> 内核态),开销大
- AQS 所有逻辑都在用户态完成,只有极端情况下才阻塞
- 可以扩展公平策略、共享模式、重入等机制
- 支持各种并发工具的构建,如锁、信号量、栅栏、计数器等
总结一下 AQS 的精髓
AQS 是一个抽象的同步器框架,本质上是:
- 一个
volatile int state变量,表示共享资源状态- 一个 FIFO 等待队列,管理被阻塞的线程
- 一个模板方法结构,子类只需要重写资源获取/释放逻辑
其核心能力包括:
✅ 支持独占与共享模式
✅ 支持公平与非公平策略
✅ 支持可重入、可中断、限时等待
✅ 支持条件队列(Condition)
✅ 高性能:用户态自旋 + 阻塞等待的混合机制


