当前位置: 首页 > news >正文

AQS 原理主线:state、CLH 队列、独占/共享与实战排查

AQS(AbstractQueuedSynchronizer)几乎是 Java 并发面试的“主干题”。

你只要把 AQS 讲清楚,很多同步工具(ReentrantLockCountDownLatchSemaphoreReentrantReadWriteLock)都能顺着讲出来。

这篇按一条主线讲透:

  • 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

它的含义由具体同步器定义:

  • ReentrantLockstate表示重入次数(0=未锁,>0=持有锁)
  • CountDownLatchstate表示计数器(>0 需要等待,0 放行)
  • Semaphorestate表示剩余许可数

关键点:

  • state 的修改必须是原子的(CAS)
  • state 的读写与队列配合,决定线程是“直接成功”还是“进入队列等待”

你可以把 state 看成“门票”:

  • 独占:门票只能被一个人持有
  • 共享:门票可以被多人消耗/归还(许可)或倒计时归零后全员放行

3. AQS 的队列:CLH 变体(你只需掌握行为)

AQS 维护一个 FIFO 的等待队列(Node 链表)。

当线程获取同步状态失败时:

  • 会被封装成一个 Node 入队
  • 进入等待
  • 前驱节点释放后,唤醒后继节点

你不用背 Node 的字段,但要会讲清这三点:

  • 为什么要排队:避免大量线程自旋浪费 CPU
  • 为什么 FIFO:保证公平性基础(是否严格公平由实现决定)
  • 为什么只唤醒后继:减少“惊群”

再补一个工程理解点:

  • 很多线程栈里看到LockSupport.park并不等于“死锁”,可能只是 AQS 的正常排队阻塞。

4. 独占模式(Exclusive):以 ReentrantLock 为例

独占模式的核心语义:

  • 同一时刻只能有一个线程持有同步状态

典型流程:

  1. 线程尝试 CAS 抢 state(从 0 改成 1)
  2. 成功 -> 获得锁
  3. 失败 -> 入队 -> park
  4. 前驱释放 -> unpark -> 再次尝试获取

与可重入的关系:

  • 如果当前线程已经持有锁,再次获取时直接state++

5. 用最小代码把 AQS“跑起来”:自定义独占锁(Mutex)

如果你想真正把 AQS 吃透,最有效的方法就是写一个最小可用的独占锁。

下面示例是一个不可重入的 Mutex(可运行骨架):

importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.AbstractQueuedSynchronizer;importjava.util.concurrent.locks.Condition;importjava.util.concurrent.locks.Lock;publicclassMuteximplementsLock{privatestaticclassSyncextendsAbstractQueuedSynchronizer{@OverrideprotectedbooleantryAcquire(intacquires){// state: 0 未占用,1 已占用if(compareAndSetState(0,1)){setExclusiveOwnerThread(Thread.currentThread());returntrue;}returnfalse;}@OverrideprotectedbooleantryRelease(intreleases){if(getState()==0)thrownewIllegalMonitorStateException();setExclusiveOwnerThread(null);setState(0);returntrue;}@OverrideprotectedbooleanisHeldExclusively(){returngetState()==1&&getExclusiveOwnerThread()==Thread.currentThread();}ConditionnewCondition(){returnnewConditionObject();}}privatefinalSyncsync=newSync();@Overridepublicvoidlock(){sync.acquire(1);}@OverridepublicbooleantryLock(){returnsync.tryAcquire(1);}@Overridepublicvoidunlock(){sync.release(1);}@OverridepublicConditionnewCondition(){returnsync.newCondition();}@OverridepublicvoidlockInterruptibly()throwsInterruptedException{sync.acquireInterruptibly(1);}@OverridepublicbooleantryLock(longtime,TimeUnitunit)throwsInterruptedException{returnsync.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相关栈

排查路径建议:

  1. 先抓线程栈:看大量线程是否卡在同一个锁竞争点
  2. 确认锁对象/锁粒度:是否把 IO、RPC 包在锁里
  3. 看是否有锁顺序问题:是否存在死锁风险

更细的关键词(jstack 常见信号):

  • java.util.concurrent.locks.LockSupport.park
  • AbstractQueuedSynchronizer$ConditionObject.await
  • AbstractQueuedSynchronizer.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 都能顺着讲
  • 工程排查核心:找到竞争点、缩小临界区、避免在锁内做慢操作
http://www.cnnetsun.cn/news/1438191.html

相关文章:

  • 我让AI开发一个完整项目,结果离谱了(全流程实测)
  • 基于 MIPS 架构的跨境充电桩链路检测与底层自愈实现
  • Step3-VL-10B-Base多模态开发环境搭建:Anaconda配置详解
  • NumPy 函数手册:数值运算
  • 【每周分享】关于BOOSTXL-3PHGANINV(TI的 三相逆变器模块)的经验分享
  • 专供普通人学的网络安全入门路线,从0到1指南,收藏这篇就够了!
  • OpenClaw - Personal AI Assistant (个人 AI 助理)
  • MLX90632红外温度传感器Arduino驱动库详解
  • MaaAssistantArknights 实战指南:从问题诊断到性能调优
  • 大数据领域分布式存储的分布式NFT数据管理
  • 造相-Z-Image游戏开发:2D素材批量生成与风格统一控制
  • 大疆Pilot上云直播实战:如何用SRS实现RTMP转WebRTC低延迟推流
  • Granite TimeSeries FlowState R1在交通流量预测中的惊艳效果:城市路口案例
  • 如何用Neorg构建合成生物学数据共享平台:终极架构设计指南
  • 【异常】Maven私有仓库依赖解析失败排查指南 [ERROR] Failed to execute goal on project example-thirdparty-app:
  • 如何通过WebAssembly优化xiaozhi-esp32-server前端性能:5个关键技巧
  • FPGA新手避坑指南:用状态机和移位寄存器两种方法实现序列检测器(附Verilog代码)
  • Esparto v3.3:ESP8266同步任务框架深度解析
  • GoCD与Vercel Analytics集成:前端性能监控自动化终极指南
  • 程序员到中年40岁了,转行搞网安还能吃到肉吗?
  • DAMO-YOLO工业落地:SMT贴片机AOI检测替代方案的成本效益分析
  • 2026大模型零基础入门到精通:全套学习路线图+实战项目,小白也能轻松掌握!
  • Python3.8镜像体验:3步搭建AI学习与Web开发环境
  • ESP32安全启动终极指南:基于xiaozhi-esp32-server实现链式验证的完整教程
  • 从top到htop:系统监控工具的进化与实战指南
  • Kettle在Windows环境下的典型故障排查与优化指南
  • STM32开发三层次:寄存器、标准库与HAL库选型指南
  • 深入ViTCE模块:OSTrack如何用‘候选消除’在单目标跟踪中又快又准?
  • 避开OpenAI API的坑:为什么你的VPN能用但代码连不上?(附区域检测工具)
  • kkFileView 3.6.0及以下版本任意文件读取漏洞复现与修复指南