文章目录
- AQS 高频深度面试题(附接地气详解)
- ✔️ 面试题 1:AQS 的全称和核心定位是什么?它解决了什么问题?
- ➡️ 面试题 2:AQS 的核心组成部分有哪些?CLH 队列的特点是什么?
- ✅ 面试题 3:AQS 支持的独占锁和共享锁有什么区别?分别对应哪些并发工具?
- 👉 面试题 4:AQS 中线程的阻塞和唤醒依赖什么?LockSupport 和 wait/notify 的核心区别?
- ✨ 面试题 5:AQS 的模板方法模式体现在哪里?为什么要设计成模板方法?
- 👉 面试题 6:ReentrantLock 的公平锁和非公平锁在 AQS 层面有什么区别?
- ‼️ 面试题 7:Semaphore 是如何基于 AQS 实现的?tryAcquireShared 返回值的含义?
- 📌 面试题 8:AQS 如何处理 LockSupport 的虚假唤醒?
- 📢 面试题 9:AQS 的条件队列和 CLH 同步队列有什么区别?
- ✔️ 面试题 10:如何基于 AQS 实现一个简易的 CountDownLatch?
以下面试题覆盖 AQS 核心考点,从基础概念到底层实现、实战应用,每道题都结合 “门禁系统” 等生活化例子拆解,答案既符合面试答题逻辑,又能体现技术深度,看完直接应对 90% 的 AQS 面试题!

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
AQS 高频深度面试题(附接地气详解)
✔️ 面试题 1:AQS 的全称和核心定位是什么?它解决了什么问题?
- 问题级别:基础必问
- 参考答案:
AQS 是AbstractQueuedSynchronizer(抽象队列同步器)的缩写,它不是锁,而是 Java 所有并发同步工具的 “通用骨架” —— 就像公司门禁的 “总控台”,本身不能开门,但定义了 “谁能进、谁排队、谁放行” 的核心规则。
它解决的核心问题:
避免重复造轮子:把 “线程排队、阻塞、唤醒、状态管理” 这些并发工具的通用逻辑抽成模板,ReentrantLock、Semaphore、CountDownLatch 等只需重写少量方法,不用重复实现底层逻辑;
保证线程安全:内置 CAS+volatile 管理同步状态(state),CLH 队列保证线程排队的公平性,解决了手动写锁时的线程安全问题;
统一阻塞 / 唤醒逻辑:基于 LockSupport 实现线程的精准阻塞和唤醒,比 wait/notify 更灵活,避免了 “空转耗 CPU”“唤醒盲盒” 等问题。
一句话总结:
- AQS 把并发同步的 “通用流程” 标准化,让开发者只需关注 “怎么抢锁、怎么放锁” 的业务逻辑,不用关心底层的排队和阻塞。
➡️ 面试题 2:AQS 的核心组成部分有哪些?CLH 队列的特点是什么?
- 问题级别:核心必问
- 详细答案
AQS 的核心由三部分组成,对应门禁系统的核心模块:

CLH 队列的核心特点(面试重点):
- 双向链表 + FIFO:线程抢锁失败会被包装成 Node 节点,通过 CAS 加到队列尾部,唤醒时从队头开始,保证 “先来后到”;
- 自旋 + 阻塞结合:节点入队后不会立即阻塞,先自旋尝试抢锁(减少上下文切换),自旋失败才调用 LockSupport.park () 阻塞;
- 非阻塞设计:队列的入队 / 出队操作全靠 CAS 实现,不用加锁,保证高并发下的性能;
- 节点状态标识:Node 节点有 CANCELLED、SIGNAL 等状态,标记线程是否取消、是否需要唤醒,避免无效的阻塞 / 唤醒。
举例子:
王二刷门禁卡(抢锁)失败,被包装成 Node 节点加到 CLH 队列尾部,队列头的李四刷完卡出门(释放锁)后,王二的节点变成队头,被 AQS 唤醒重新抢锁。
✅ 面试题 3:AQS 支持的独占锁和共享锁有什么区别?分别对应哪些并发工具?
- 问题级别:核心必问
- 详细答案:
AQS 的核心是通过重写不同的钩子方法,实现 “独占锁” 和 “共享锁” 两种模式,对应门禁的两种通行规则:

关键区别总结:
- 独占锁的tryAcquire返回 boolean(成功 / 失败),共享锁的tryAcquireShared返回 int(>=0 = 成功,<0 = 失败);
- 独占锁释放时只需唤醒一个线程,共享锁可能唤醒多个(比如读锁释放,所有等待读锁的线程都能获锁);
- 独占锁存在 “锁竞争”,共享锁在许可充足时无竞争(比如 Semaphore 有 10 个许可,前 10 个线程直接获锁)。
👉 面试题 4:AQS 中线程的阻塞和唤醒依赖什么?LockSupport 和 wait/notify 的核心区别?
- 问题级别:深度必问
- 详细答案:
第一问:AQS 的线程阻塞 / 唤醒依赖 LockSupport
AQS 的 CLH 队列中,线程抢锁失败后,会调用LockSupport.park()阻塞;锁释放时,调用LockSupport.unpark(队头线程)精准唤醒 ——LockSupport 是 AQS 的 “手脚”,所有线程的阻塞 / 唤醒最终都靠它实现。
第二问:LockSupport vs wait/notify(面试高频对比)
用 “门禁对讲机” 和 “食堂喊号” 对比,核心区别如下:

举例子:
- 用 wait/notify 时,食堂阿姨先喊 “来打饭”(notify),但食客还没到窗口(没调用 wait),食客到了后会一直等;
- 用 LockSupport 时,保安先给王二开门禁权限(unpark),王二 2 秒后刷卡(park),依然能进门,不会白等。
✨ 面试题 5:AQS 的模板方法模式体现在哪里?为什么要设计成模板方法?
- 问题级别:设计思想必问
- 详细答案
第一问:模板方法模式的体现
AQS 是模板方法模式的经典应用,把方法分成两类:
- 模板方法(已实现,不可重写):定义并发同步的通用流程,比如acquire()(抢锁流程)、release()(释放锁流程)、acquireShared()(共享锁抢锁流程)—— 这些方法内置了 “抢锁→入队→阻塞→唤醒” 的完整逻辑;
- 钩子方法(空实现 / 默认实现,需子类重写):定义 “怎么抢锁、怎么放锁” 的具体规则,比如tryAcquire()、tryRelease()、tryAcquireShared()—— 子类(比如 ReentrantLock 的 Sync)只需重写这些方法,就能实现不同的锁逻辑。
模板方法流程示例(以独占锁 acquire 为例):
// AQS的模板方法(固定流程)
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 钩子方法:子类实现“怎么抢锁”
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { // 模板逻辑:入队+阻塞
selfInterrupt();
}
}
第二问:设计成模板方法的原因
- 标准化流程:保证所有并发工具的底层逻辑一致(比如排队、阻塞规则),避免不同工具的 “逻辑碎片化”;
- 降低开发成本:开发者只需关注业务逻辑(比如 ReentrantLock 的可重入规则、Semaphore 的许可规则),不用重复写底层的排队 / 阻塞代码;
- 便于维护和扩展:AQS 的核心流程(比如 CLH 队列管理)集中维护,修改一处就能适配所有基于 AQS 的工具。
一句话总结:
- 模板方法模式让 AQS“定流程、留接口”,既保证了底层逻辑的统一,又给上层工具足够的定制灵活性。
👉 面试题 6:ReentrantLock 的公平锁和非公平锁在 AQS 层面有什么区别?
- 问题级别:实战必问
ReentrantLock 的公平 / 非公平锁,核心区别体现在 AQS 的tryAcquire方法(抢锁逻辑)上,对应门禁的 “排队规则”.
1. 公平锁(FairSync):严格按 CLH 队列顺序抢锁
抢锁时会先检查 CLH 队列是否有等待的线程,只有队头线程能抢锁 —— 就像门禁严格按排队顺序刷卡,不允许插队。
// 公平锁的tryAcquire核心逻辑
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键:先检查队列是否有等待线程,没有才抢锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 可重入逻辑(略)
return false;
}
2. 非公平锁(NonfairSync):允许 “插队” 抢锁
抢锁时不检查队列,直接用 CAS 尝试抢锁,抢不到再进队列 —— 就像门禁允许 “没排队的人直接刷卡”,效率更高,但可能导致线程饥饿。
// 非公平锁的tryAcquire核心逻辑
protected final boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) { // 关键:直接抢锁,不检查队列
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
// 可重入逻辑(略)
return false;
}
核心区别总结:

‼️ 面试题 7:Semaphore 是如何基于 AQS 实现的?tryAcquireShared 返回值的含义?
- 问题级别:深度必问
- 详细答案:
第一问:Semaphore 的 AQS 实现逻辑
Semaphore 是 AQS 共享锁的典型应用,核心是把 AQS 的 state 作为 “许可数”,对应门禁的 “通行权限数”(比如 10 个权限 = 一次能进 10 个人):
- 初始化:new Semaphore(10) → AQS 的 state=10(10 个许可);
- 获取许可(acquire ()):调用 AQS 的acquireShared(1),触发tryAcquireShared——CAS 把 state 减 1,剩余许可≥0 则获锁成功,否则进 CLH 队列阻塞;
- 释放许可(release ()):调用 AQS 的releaseShared(1),触发tryReleaseShared——CAS 把 state 加 1,释放成功后唤醒队列中等待的线程。
核心源码(简化版):
// Semaphore的AQS子类
static final class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) { setState(permits); } // state=许可数
// 共享锁抢许可
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
// 剩余许可<0则失败,否则CAS修改state
if (remaining < 0 || compareAndSetState(available, remaining)) {
return remaining;
}
}
}
// 共享锁释放许可
protected boolean tryReleaseShared(int releases) {
for (;;) {
int available = getState();
int next = available + releases;
if (compareAndSetState(available, next)) {
return true;
}
}
}
}
核心源码(简化版):
// Semaphore的AQS子类
static final class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) { setState(permits); } // state=许可数
// 共享锁抢许可
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
// 剩余许可<0则失败,否则CAS修改state
if (remaining < 0 || compareAndSetState(available, remaining)) {
return remaining;
}
}
}
// 共享锁释放许可
protected boolean tryReleaseShared(int releases) {
for (;;) {
int available = getState();
int next = available + releases;
if (compareAndSetState(available, next)) {
return true;
}
}
}
}
第二问:tryAcquireShared 返回值的含义
tryAcquireShared的返回值是 “剩余许可数”,AQS 根据返回值判断线程是否需要排队:
- 返回值 ≥ 0:抢许可成功,线程无需排队,直接执行;
- 返回值 < 0:抢许可失败,线程进入 CLH 队列阻塞,等待其他线程释放许可。
举例子:
Semaphore 有 10 个许可,第 11 个线程调用 acquire (),tryAcquireShared返回 - 1,线程进入队列;当有线程释放许可后,state=1,唤醒队头线程,tryAcquireShared返回 0,线程获锁成功。
📌 面试题 8:AQS 如何处理 LockSupport 的虚假唤醒?
问题级别:深度必问
详细答案:
第一步:先明确 “虚假唤醒”
- 虚假唤醒是指线程在没有被 unpark 的情况下,自己从 park () 中醒来 —— 就像门禁对讲机串台,没喊王二,但王二自己醒了过来。LockSupport 和 wait 都存在虚假唤醒,AQS 的解决思路是 “死循环 + 业务条件重试”。
第二步:AQS 的解决逻辑(核心在 acquireQueued 方法)
- 队列中的线程被唤醒后,不会直接获锁,而是进入死循环,重新检查 “是否能抢锁”,就算虚假唤醒,也会重新判断业务条件:
核心逻辑总结:
- 虚假唤醒后,线程回到死循环;
- 重新检查 “是否是队头线程”+“是否能抢锁”(业务条件);
- 若条件不满足,再次调用 LockSupport.park () 阻塞;
- 只有条件满足,才退出循环,获锁成功。
一句话总结:
AQS 不试图避免虚假唤醒,而是用 “死循环 + 业务条件重试” 兜底,确保就算唤醒无效,线程也会重新检查条件,不会错误执行。
📢 面试题 9:AQS 的条件队列和 CLH 同步队列有什么区别?
问题级别:深度必问
详细答案:
AQS 有两个队列:CLH 同步队列(核心)和条件队列(配合 Condition),对应门禁的 “普通排队区” 和 “VIP 候场区”,核心区别如下:

举例子(ReentrantLock 的 Condition):
- 王二拿到锁后,发现会议室没空(业务条件不满足),调用await()—— 王二的线程从 CLH 队列(已获锁,无节点)进入条件队列,释放锁;
- 李四用完会议室,调用signal()—— 王二的线程从条件队列转移到 CLH 队列尾部;
- 李四释放锁后,CLH 队列的王二被唤醒,重新抢锁,抢到后继续执行。
✔️ 面试题 10:如何基于 AQS 实现一个简易的 CountDownLatch?
- 问题级别:手写实战必问
- 详细答案:
CountDownLatch 是 AQS 共享锁的应用,核心是把 state 作为 “倒计时数”,state 减到 0 后,所有等待线程被唤醒 —— 就像门禁的 “集合锁”,等 10 个人到齐(countDown 10 次),才开门放行。
手写简易 CountDownLatch 代码(完整可运行):
package cn.tcmeta.aqs;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import static java.lang.Thread.sleep;
/**
* @author: laoren
* @description: 自定义CountDownLatch
* @version: 1.0.0
*/
public class MyCountDownLatch {
// AQS共享锁子类
private final Sync sync;
// 构造方法:指定倒计时数(state=count)
public MyCountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count不能为负");
this.sync = new Sync(count);
}
// AQS核心逻辑:共享锁+倒计时
private static class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count); // state=倒计时数
}
// 共享锁抢锁逻辑:state=0才允许获锁
@Override
protected int tryAcquireShared(int acquires) {
// state=0返回1(成功),否则返回-1(失败)
return getState() == 0 ? 1 : -1;
}
// 共享锁释放逻辑:countDown一次,state减1
@Override
protected boolean tryReleaseShared(int releases) {
for (; ; ) {
int current = getState();
if (current == 0) return false; // 已经减到0,无需操作
int next = current - 1;
if (compareAndSetState(current, next)) {
return next == 0; // 减到0时返回true,触发唤醒所有线程
}
}
}
}
// 等待倒计时结束(调用AQS的共享锁抢锁)
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 倒计时减1(调用AQS的共享锁释放)
public void countDown() {
sync.releaseShared(1);
}
// 测试代码
public static void main(String[] args) throws InterruptedException {
MyCountDownLatch latch = new MyCountDownLatch(3); // 等3个线程完成
// 启动3个工作线程
for (int i = 1; i <= 3; i++) {
int finalI = i;
new Thread(() -> {
System.out.println("线程" + finalI + ":开始工作");
try {
sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("线程" + finalI + ":工作完成,倒计时减1");
latch.countDown(); // 倒计时减1
}).start();
}
// 主线程等待
System.out.println("主线程:等待所有线程完成...");
latch.await();
System.out.println("主线程:所有线程完成,开始汇总结果");
}
}

核心逻辑拆解:
- 初始化:new MyCountDownLatch(3) → AQS 的 state=3;
- 工作线程调用countDown() → tryReleaseShared把 state 减 1,减到 0 时返回 true,AQS 唤醒所有等待线程;
- 主线程调用await() → tryAcquireShared检查 state 是否为 0,不为 0 则进入 CLH 队列阻塞,直到 state=0 才获锁成功。
面试答题技巧:
- 手写时不用写完整代码,只需讲清 “state = 倒计时数”“tryAcquireShared 判断 state 是否为 0”“tryReleaseShared 减 state” 三个核心点,就能体现对 AQS 的掌握。



176万+

被折叠的 条评论
为什么被折叠?



