最近在准备面试,正把平时积累的笔记、项目中遇到的问题与解决方案、对核心原理的理解,以及高频业务场景的应对策略系统梳理一遍,既能加深记忆,也能让知识体系更扎实,供大家参考,欢迎讨论。
AbstractQueuedSynchronizer 就是:
👉 "一个抽象的、基于队列的同步器"。
- 抽象:自己不能直接用,得继承。
- 排队:管理等待线程的队列。
- 同步器:用于实现各种同步工具(可重入锁、信号量、闭锁...)。
在 Java 并发编程里,虽然平时我们写代码常用的是 ReentrantLock(独占锁)
、CountDownLatch(共享锁)
、Semaphore
,但它们的底层都是靠 AQS 实现的。
1. AQS 是什么?
一句话:
👉 AQS 就是一个排队管家。
- state :state 是个抽象的同步状态,不同场景下有不同解释。
- 在 ReentrantLock 里,
state
表示 锁的重入次数 (0
代表未加锁,>0
表示已加锁,并统计重入层数)。 - 在 CountDownLatch 里,
state
表示 计数值(倒计时还有几个)。 - 在 Semaphore 里,
state
表示 剩余的许可数量。
- 在 ReentrantLock 里,
- 队列 :没抢到资源的线程,会被放进一个 FIFO 队列。
- 阻塞与唤醒 :没轮到的线程,先
park
(睡觉);轮到时,再unpark
(叫醒)。
AQS 不管具体规则,只提供排队机制。子类自己定义"怎么抢""怎么放",AQS 负责排队+调度"。
2. 核心流程
拿独占锁为例:
-
线程调用
lock()
,底层走到 AQS 的acquire()
。 -
tryAcquire()
:尝试直接抢锁(子类自己实现)。 -
如果抢不到:
- 把当前线程打包成 Node → 入队。
- 调用
LockSupport.park()
把线程挂起。
-
当持有锁的线程
unlock()
:- AQS 调用
tryRelease()
释放资源。 - 唤醒队列里下一个节点的线程。
- AQS 调用
-
被唤醒的线程重新去尝试抢锁。
3. ReentrantLock 如何用 AQS?
ReentrantLock
就是基于 AQS 实现的独占锁(独占模式),它的实现类 Sync
继承了 AQS,并重写了关键方法:
tryAcquire(int arg)
:如果没人占用锁,或者自己重入 → 修改 state + 1 并返回 true。tryRelease(int arg)
: state - 1,若为 0 表示锁完全释放。
剩下的排队、阻塞、唤醒,全由 AQS 管理。
4. CountDownLatch 如何用 AQS?
CountDownLatch
基于 AQS 的 共享模式(Shared) 实现,它的内部类 Sync
继承了 AQS,并重写了关键方法:
tryAcquireShared(int arg)
:检查 state(计数器)是否为 0,如果为 0 → 返回正值,表示可以获取;否则返回负值,表示线程需要排队等待。tryReleaseShared(int arg)
:原子将 state 减 1,如果减到 0 → 返回 true,AQS 会唤醒所有等待线程。
剩下的排队、阻塞、唤醒,全由 AQS 管理。
很好的抽象,该交给子类去扩展的就封装抽象方法,让子类决定是否支持共享模式