AQS(AbstractQueuedSynchronizer)几乎是 Java 并发面试的"主干题"。
你只要把 AQS 讲清楚,很多同步工具(ReentrantLock、CountDownLatch、Semaphore、ReentrantReadWriteLock)都能顺着讲出来。
这篇按一条主线讲透:
- AQS 解决什么问题
state是什么- 线程怎么排队(CLH 变体)
- 独占/共享的差异
- 常见追问 + 线上排查
你可以用一句话把 AQS 的价值说清:
- 把"抢不到就排队、排队就阻塞、释放就唤醒"的通用流程做成框架,只把 state 的语义留给子类。
1. AQS 解决什么问题
AQS 的目标不是"提供锁",而是提供一个可复用的同步器框架:
- 用一个整数状态
state表示同步状态 - 用一个 FIFO 队列管理获取失败的线程
- 通过模板方法把"排队/阻塞/唤醒"这套通用流程复用出来
你可以把 AQS 理解成:
- 状态机(state) + 等待队列(queue) + 阻塞/唤醒(park/unpark)
工程上可以再落到一个更直接的心智模型:
- 快路径:CAS 抢 state 成功(无竞争)
- 慢路径:CAS 失败 -> 入队 -> park -> 被唤醒后重试
2. state:同步器的核心状态
AQS 内部维护一个 volatile int state。
它的含义由具体同步器定义:
ReentrantLock:state表示重入次数(0=未锁,>0=持有锁)CountDownLatch:state表示计数器(>0 需要等待,0 放行)Semaphore:state表示剩余许可数
关键点:
- state 的修改必须是原子的(CAS)
- state 的读写与队列配合,决定线程是"直接成功"还是"进入队列等待"
你可以把 state 看成"门票":
- 独占:门票只能被一个人持有
- 共享:门票可以被多人消耗/归还(许可)或倒计时归零后全员放行
3. AQS 的队列:CLH 变体(你只需掌握行为)
AQS 维护一个 FIFO 的等待队列(Node 链表)。
当线程获取同步状态失败时:
- 会被封装成一个 Node 入队
- 进入等待
- 前驱节点释放后,唤醒后继节点
你不用背 Node 的字段,但要会讲清这三点:
- 为什么要排队:避免大量线程自旋浪费 CPU
- 为什么 FIFO:保证公平性基础(是否严格公平由实现决定)
- 为什么只唤醒后继:减少"惊群"
再补一个工程理解点:
- 很多线程栈里看到
LockSupport.park并不等于"死锁",可能只是 AQS 的正常排队阻塞。
4. 独占模式(Exclusive):以 ReentrantLock 为例
独占模式的核心语义:
- 同一时刻只能有一个线程持有同步状态
典型流程:
- 线程尝试 CAS 抢 state(从 0 改成 1)
- 成功 -> 获得锁
- 失败 -> 入队 -> park
- 前驱释放 -> unpark -> 再次尝试获取
与可重入的关系:
- 如果当前线程已经持有锁,再次获取时直接
state++
5. 用最小代码把 AQS"跑起来":自定义独占锁(Mutex)
如果你想真正把 AQS 吃透,最有效的方法就是写一个最小可用的独占锁。
下面示例是一个不可重入的 Mutex(可运行骨架):
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int acquires) {
// state: 0 未占用,1 已占用
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1 && getExclusiveOwnerThread() == Thread.currentThread();
}
Condition newCondition() {
return new ConditionObject();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
}
你从这个例子里要抓住两点:
acquire/release是 AQS 帮你做的"排队/park/unpark"框架- 你只需要定义
tryAcquire/tryRelease的 state 语义
6. 共享模式(Shared):以 CountDownLatch/Semaphore 为例
共享模式的核心语义:
- 同一时刻允许多个线程同时通过(取决于 state/许可数)
典型例子:
CountDownLatch:state=0 时所有 await 线程放行Semaphore:state 代表许可数,拿到许可才能通过
共享模式的一个重要点:
- 释放时可能需要唤醒多个等待线程(例如 latch 归零)
共享模式你可以用 latch 的语义来记:
state > 0:所有 await 都要排队state == 0:所有 await 都直接通过
7. AQS 的"模板方法"你要会怎么说
面试讲法(不背源码也能讲清):
- AQS 负责:排队、阻塞、唤醒、超时/中断处理
- 子类负责:
tryAcquire/tryRelease(独占)tryAcquireShared/tryReleaseShared(共享)
所以你可以把它说成:
- AQS 把通用的等待队列与线程调度做成框架,把"能不能拿到同步状态"的判断留给子类。
8. 常见追问(高频)
8.1 为什么用 CAS + 队列,而不是纯 synchronized
- CAS 快路径:无竞争时极低成本
- 队列阻塞:竞争激烈时避免 CPU 自旋浪费
8.2 AQS 怎么处理中断/超时
park等待期间线程可能被中断- AQS 会在合适时机检查中断标记,并按 API 语义决定:
- 抛
InterruptedException - 或返回失败
- 抛
8.3 公平锁与非公平锁差在哪
- 公平:倾向让队列头先获取
- 非公平:允许"插队"抢占,吞吐更高但延迟抖动可能更大
你可以再补一句面试加分点:
- 非公平锁会先 CAS 抢一次 state(插队),失败了再走队列。
9. 线上排查:怎么判断是不是 AQS 锁竞争
你在线上遇到 RT 抖动/吞吐下降时,常见信号:
- 大量线程
BLOCKED/WAITING (parking) jstack中出现java.util.concurrent.locks.AbstractQueuedSynchronizer相关栈
排查路径建议:
- 先抓线程栈:看大量线程是否卡在同一个锁竞争点
- 确认锁对象/锁粒度:是否把 IO、RPC 包在锁里
- 看是否有锁顺序问题:是否存在死锁风险
更细的关键词(jstack 常见信号):
java.util.concurrent.locks.LockSupport.parkAbstractQueuedSynchronizer$ConditionObject.awaitAbstractQueuedSynchronizer.acquire/acquireQueued
10. 面试表达(30 秒讲清楚)
- AQS 是同步器框架,用
state表示同步状态,用 FIFO 队列管理获取失败的线程。 - 无竞争走 CAS 快路径,竞争时入队
park,释放时unpark后继。 - 独占模式对应 ReentrantLock(同一时刻一个线程),共享模式对应 CountDownLatch/Semaphore(允许多个线程通过)。
- AQS 负责排队与线程调度,子类通过
tryAcquire/tryRelease定义 state 的语义。 - 排查锁竞争:看线程
parking、jstack 是否落在 AQS 相关栈,再回到锁粒度与临界区。
11. 总结
- AQS = state + 队列 + park/unpark
- 独占/共享是两种不同的获取/释放语义
- 讲清 AQS,ReentrantLock/CountDownLatch/Semaphore 都能顺着讲
- 工程排查核心:找到竞争点、缩小临界区、避免在锁内做慢操作