文章目录
零、引入
“面试官最后问‘ReentrantLock 底层靠啥实现的?’我答‘锁啊’,再问‘AQS 懂吗?’我直接卡壳,全程‘不知道’三连,出门就收到‘不合适’的短信……” 王二攥着面试反馈,蹲在工位角落,刚面的大厂 offer 就这么飞了,连面试官摇头的样子都记得清清楚楚。
隔壁哇哥嚼着口香糖凑过来,一把抢过反馈单:“你光会用 ReentrantLock 不行啊,*AQS 才是并发工具的‘骨架’——ReentrantLock、CountDownLatch 这些全是靠它撑起来的. *今天起分三篇给你讲,第一篇先把 AQS 的‘魂’给你扒出来,用公司门禁系统给你讲明白,下次面试让你把面试官说懵。”
点赞 + 关注,跟着哇哥和王二,从 “门禁管理员” 到手写代码,彻底搞懂 AQS 的核心,并发面试再不怕!
一、先破案:王二的 “知识盲区”,AQS 到底是个啥?

王二打开 IDE,指着 ReentrantLock 的代码:“我知道 lock () 能加锁,unlock () 能解锁,可面试官一追问‘底层怎么实现的’,我就懵了。”
哇哥敲了敲键盘,调出 AQS 的类图:“AQS 全名叫 AbstractQueuedSynchronizer,翻译过来是‘抽象队列同步器’—— 听着玄乎,其实就是个‘并发工具的模板’。你可以把它想成公司的门禁管理系统:
- 门禁系统(AQS):规定了谁能进公司(获取锁)、谁要排队(等待队列)、进不去怎么办(阻塞);
- 员工卡(同步状态):一张卡代表一个‘锁’,卡被人拿走了(状态 = 1),其他人就得排队;
- 排队区(CLH 队列):没拿到卡的人,按顺序排好队,等前面的人还卡(释放锁)再轮;
- 具体部门(ReentrantLock/CountDownLatch):不同部门用门禁系统的规则不一样 —— 研发部要‘独占卡’(一个人用),人事部要‘共享卡’(多个人一起用)。”
王二眼睛一亮:“也就是说,AQS 是个‘通用门禁框架’,ReentrantLock 这些工具只是在这个框架上套了层‘部门规则’?”
“太对了!” 哇哥点头,“AQS 把并发工具的共性(排队、阻塞、唤醒)都写好了,剩下的‘部门规则’(比如独占还是共享、怎么判断能不能拿卡)留给具体工具自己实现 —— 这就是模板方法模式,AQS 是模板,ReentrantLock 是具体实现。”
二、AQS 的核心三要素:同步状态 + CLH 队列 + 条件队列
哇哥拉过一张纸,画了个简易的 AQS 结构图,王二一看就懂了 —— 核心就三样东西,连起来就是完整的 “门禁流程”。
✨ 同步状态:AQS 的 “核心开关”(员工卡)
AQS 里有个state变量,是 volatile 修饰的,专门记录 “锁的状态”,这是判断能不能拿锁的关键:

- 对于 ReentrantLock(独占锁):state=0代表锁没人用,state=1代表锁被占用,state>1代表重入(同一个人拿了多次卡);
- 对于 CountDownLatch(共享锁):state=N代表还有 N 个任务没完成,state=0代表所有任务完成,等待的线程可以进。
“这就像门禁系统的‘卡状态显示器’,” 哇哥比喻,“显示‘空’(state=0)就能拿卡,显示‘已占用’(state=1)就得排队。”
AQS 给state提供了三个核心操作,都是原子的,靠 CAS 实现(保证多线程安全):
getState():看当前状态(查显示器);
setState(int newState):改状态(手动掰显示器,只有拿到卡的人能改);
compareAndSetState(int expect, int update):CAS 改状态(比如期望是 0,改成 1,改成功说明拿到卡了)。
📢 2. CLH 队列:AQS 的 “排队区”(按顺序等卡)
CLH 队列是个双向链表,专门存 “没拿到锁的线程”—— 就像公司门口的排队队伍,按先来后到顺序排,前面的人还了卡,就叫醒下一个人。
队列里的每个节点(Node)都有这些关键信息:

thread:排队的线程(哪个员工在等);
prev/next:前后节点的指针(排队的人互相拉着手,不插队);
waitStatus:节点状态(比如 “等待中”“已取消”“要唤醒”)。
“重点是‘双向链表’,” 哇哥强调,“既能往前找前面的人,也能往后找后面的人,方便移除取消排队的线程,也方便唤醒下一个人。”
➡️ 条件队列:AQS 的 “特殊候场区”(等通知再排队)

除了 CLH 主队列,AQS 还有 “条件队列”—— 比如 ReentrantLock 的 Condition,就靠这个实现。
“这就像公司的‘会议室候场区’,” 哇哥比喻,“你拿到门禁卡进了公司(获取锁),但会议室有人(条件不满足),就去候场区等,等里面的人出来喊你(signal),你再回到主队列重新排队拿会议室的钥匙。”
条件队列也是个链表,线程调用await()就进条件队列,调用signal()就从条件队列移回 CLH 主队列,等待拿锁。
三、AQS 的核心工作流程:拿锁→排队→唤醒(门禁系统实战)

哇哥用 “研发部员工小王拿门禁卡” 的场景,给王二讲透 AQS 的完整流程 —— 这也是 ReentrantLock 的底层逻辑:
✔️ 流程 1:拿锁(尝试拿员工卡)
小王(线程)到门禁口,先查状态显示器(getState ()):
- 如果显示 “空”(state=0),用 CAS 把状态改成 1(compareAndSetState (0,1)),改成功就拿到卡,进公司(获取锁成功);
- 如果显示 “已占用”(state=1),再看是不是自己拿的(ReentrantLock 支持重入)—— 如果是,就把 state 加 1(比如 state=2),不用排队;
- 如果是别人拿的,就去 CLH 队列排队。
📌 流程 2:排队(进 CLH 队列等)
小王没拿到卡,AQS 会做三件事:
- 把小王包装成一个 Node 节点,加到 CLH 队列的末尾(CAS 加,保证线程安全);
- 让小王的线程阻塞(用 LockSupport.park (),比 wait () 更灵活);
- 释放 CPU 资源,小王的线程进入 “阻塞状态”,不再抢锁。
“这一步很关键,” 哇哥说,“线程阻塞后就不占 CPU 了,不会像王二之前写的空转代码那样把 CPU 跑满。”
✅ 流程 3:唤醒(前面的人还卡,叫醒下一个)
拿卡的人(线程)离开公司,执行 unlock ():
- 把 state 改成 0(释放卡);
- 从 CLH 队列的头节点开始,找下一个 “等待中” 的节点(小王);
- 用 LockSupport.unpark () 唤醒小王的线程;
- 小王被唤醒后,再次尝试 CAS 改 state 拿锁 —— 这次大概率能拿到,因为前面的人已经还卡了。
“整个流程就是‘拿锁→排队→唤醒→再拿锁’,”
王二总结,“AQS 把这些流程都写死了,ReentrantLock 只需要告诉 AQS‘怎么判断能不能拿锁’‘拿锁后怎么改状态’就行?”
“完全正确!” 哇哥竖大拇指,“这就是模板方法模式的精髓 ——AQS 定义骨架,具体工具填充细节。”
四、条件队列:门禁的 “VIP 候场区”
如果公司有 VIP 通道,普通排队区之外还要有个 VIP 候场区 —— 这就是 AQS 的 “条件队列”,配合 Condition 使用,对应await()和signal()方法。
➡️ 条件队列的核心逻辑(VIP 候场场景)
- VIP 用户王二进门后,发现会议室被占了(业务条件不满足),就去 VIP 候场区等着(调用 await (),从 CLH 队列转到条件队列);
- 会议室空了(业务条件满足),前台小姐姐喊一声(调用 signal ()),王二从 VIP 候场区回到 CLH 队列的队头,优先进门;
- 条件队列可以有多个,比如 “会议室候场区”“打印机房候场区”,对应不同的业务条件。
五、手写简易 AQS:自己实现一个 “独占锁”
为了让王二彻底掌握,哇哥带着他手写一个基于 AQS 的独占锁 —— 不用 ReentrantLock,自己造一个,核心逻辑和 ReentrantLock 一样。
‼️步骤 1:自定义锁,继承 AQS
AQS 是抽象类,必须重写两个核心方法(这就是 “填充细节”):
- tryAcquire(int arg):尝试拿锁(告诉 AQS 怎么判断能不能拿卡);
- tryRelease(int arg):尝试释放锁(告诉 AQS 怎么还卡)。
package cn.tcmeta.aqs;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* @author: laoren
* @description: // 基于AQS实现的独占锁
* @version: 1.0.0
*/
public class TCLock implements Lock {
// 核心:AQS的子类,重写核心方法
private final Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer {
// 1. 尝试获取锁(对应门禁刷卡)
@Override
protected boolean tryAcquire(int arg) {
// 用CAS修改state:从0改成1(arg=1,代表获取1个锁资源)
if (compareAndSetState(0, arg)) {
// 记录当前获锁的线程(防止别人抢)
setExclusiveOwnerThread(Thread.currentThread());
return true; // 刷卡成功,进门
}
// 支持可重入:当前线程已经获锁,再次获取时state+1
if (getExclusiveOwnerThread() == Thread.currentThread()) {
setState(getState() + arg);
return true;
}
return false; // 刷卡失败,去排队
}
// 2. 尝试释放锁(对应出门刷开)
@Override
protected boolean tryRelease(int arg) {
// 只有获锁的线程能释放
if (getExclusiveOwnerThread() != Thread.currentThread()) {
throw new IllegalMonitorStateException("没刷卡的人不能开门!");
}
// 可重入释放:state减到0才真释放
int newState = getState() - arg;
boolean free = (newState == 0);
if (free) {
setExclusiveOwnerThread(null); // 清空获锁线程
}
setState(newState);
return free; // 释放成功,通知下一个人
}
// 3. 判断是否持有锁(辅助方法)
@Override
protected boolean isHeldExclusively() {
// 必须由当前线程持有且 state > 0 才算真正持有
return getState() > 0 && getExclusiveOwnerThread() == Thread.currentThread();
}
// 生成Condition(对应VIP候场区)
Condition newCondition() {
return new ConditionObject();
}
}
// 以下是Lock接口的实现,全委托给Sync(AQS子类)
@Override
public void lock() {
sync.acquire(1); // 调用AQS的acquire,触发tryAcquire+排队
}
@Override
public void unlock() {
sync.release(1); // 调用AQS的release,触发tryRelease+唤醒
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time)); // 正确传递超时时间
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public java.util.concurrent.locks.Condition newCondition() {
return sync.newCondition();
}
private static volatile int stock = 10;
private static volatile TCLock lock = new TCLock();
/**
* 扣减库存
*/
private static void deductStock() {
lock.lock();
try {
if (stock > 0) {
stock--;
System.out.println(Thread.currentThread().getName() + ":成功扣减库存,剩余:" + stock);
} else {
System.out.println(Thread.currentThread().getName() + ":库存不足");
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) { // 修改为主函数正确格式
for (int i = 0; i < 10; i++) {
new Thread(TCLock::deductStock, "线程" + i).start();
}
}
}

👉👉👉 核心亮点(哇哥划重点)
- 我们只重写了 AQS 的tryAcquire和tryRelease,告诉 AQS “怎么抢锁”“怎么放锁”;
- 线程排队、阻塞、唤醒的逻辑,全是 AQS 自带的,不用我们写一行 LockSupport 代码;
- 支持可重入:同一线程多次 lock () 不会死锁,state 会累加,解锁时累减到 0 才释放。
六、AQS 的核心价值(王二记满小本本)
王二把 AQS 的核心价值总结成 3 句话,贴在显示器上:
- **标准化骨架:**把锁的 “排队、阻塞、唤醒” 这些重复逻辑抽成通用骨架,避免每个锁都重复造轮子;
- **灵活定制:**通过重写tryAcquire/tryRelease等方法,轻松实现独占锁(ReentrantLock)、共享锁(Semaphore);
- **线程安全:**内置 CAS、volatile 保证同步状态的线程安全,CLH 队列保证排队公平性。
📢 哇哥的血泪彩蛋
“我刚工作时,自己用 synchronized+wait 写锁,” 哇哥捂脸,“结果没处理好排队逻辑,线程经常‘插队’,库存超卖了好几千件 —— 后来换成 AQS 写的 ReentrantLock,只关心业务逻辑,再也没出过错。记住:不要和 AQS 抢活干,它比你懂并发!”
关注我,下一篇我们扒透 AQS 的 “手脚”——LockSupport,看看线程是怎么被精准阻塞和唤醒的,再手写个 Semaphore,让你彻底掌握 AQS 的实战用法!




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



