一、AQS 是什么?
AQS (AbstractQueuedSynchronizer) 是一个抽象类,位于 java.util.concurrent.locks
包下。它为构建锁(Locks) 和同步器(Synchronizers) 提供了一个框架。
简单来说,AQS 就是一个“并发工具制造工厂”的核心引擎。JUC 包中几乎所有重要的同步工具,其底层实现都依赖于 AQS。
它的主要思想是:
- 状态管理: 使用一个
int
类型的成员变量state
来表示同步状态(如:锁是否被占用、剩余许可证数量、倒计时数值等)。 - 队列管理: 内部维护一个FIFO 的双向队列(CLH 队列的变种),用于管理等待获取共享资源的线程。当线程获取资源失败时,会被封装成一个节点(Node)加入队列并阻塞;当资源可用时,按 FIFO 顺序唤醒队列中的线程。
- 模板方法模式: AQS 本身定义了获取和释放资源的主流程(如
acquire
,release
,acquireShared
,releaseShared
),但将如何判断状态、如何修改状态的具体逻辑(即tryAcquire
,tryRelease
,tryAcquireShared
,tryReleaseShared
等方法)留给子类去实现。这是一种典型的模板方法设计模式。
二、AQS 与前面提到的锁和同步器的关系
核心关系:AQS 是这些并发工具的底层实现基础。
我们可以把 AQS 想象成一个“发动机”,而 ReentrantLock
、CountDownLatch
等就是装上了不同“外壳”和“控制面板”的“汽车”或“机器”。它们共享同一个核心动力系统(AQS),但对外提供的功能和接口不同。
下面具体说明:
2.1 AQS 与 ReentrantLock
- 关系:
ReentrantLock
的核心实现类ReentrantLock.Sync
(及其子类NonfairSync
和FairSync
)直接继承自 AQS。 - 如何工作:
state
变量表示锁的重入次数。0 表示锁未被占用,大于 0 表示锁已被占用,数值即为重入次数。tryAcquire(int acquires)
:子类实现。尝试获取锁。如果是非公平锁,会直接尝试 CAS 修改state
;如果是公平锁,会先检查队列中是否有等待线程。如果当前线程已持有锁,则增加state
(重入)。tryRelease(int releases)
:子类实现。释放锁,减少state
。如果state
减到 0,表示锁完全释放,返回true
,AQS 会唤醒队列中的下一个线程。lock()
方法最终会调用 AQS 的acquire(1)
方法。unlock()
方法最终会调用 AQS 的release(1)
方法。
- 总结:
ReentrantLock
的所有核心逻辑(排队、阻塞、唤醒、重入)都是由 AQS 提供的框架来完成的,它只需要实现“如何判断和修改锁状态”这个核心逻辑。
2.2 AQS 与 ReentrantReadWriteLock
- 关系:
ReentrantReadWriteLock
的核心实现类ReentrantReadWriteLock.Sync
也继承自 AQS。 - 如何工作:
state
变量被按位拆分使用。高 16 位表示读锁的重入次数,低 16 位表示写锁的重入次数。tryAcquire(int acquires)
:实现写锁的获取逻辑(独占模式)。tryAcquireShared(int acquires)
:实现读锁的获取逻辑(共享模式)。只要没有写锁或只有当前线程持有写锁,就可以获取读锁。tryRelease(int releases)
:释放写锁。tryReleaseShared(int releases)
:释放读锁。
- 总结: 读写锁的复杂状态管理(读读共享、读写互斥、写写互斥、重入)都是在 AQS 的框架下,通过巧妙地操作
state
的高低位来实现的。
2.3 AQS 与 CountDownLatch
- 关系:
CountDownLatch
的内部类Sync
继承自 AQS。 - 如何工作:
state
变量直接表示倒计时的数值(即构造函数传入的count
)。tryAcquireShared(int acquires)
:实现await()
的逻辑。如果state == 0
,返回 1(表示获取成功,线程可以继续);否则返回 -1(表示获取失败,线程需要入队等待)。tryReleaseShared(int releases)
:实现countDown()
的逻辑。通过 CAS 将state
减 1。如果减到 0,返回true
,AQS 会唤醒所有在await()
中等待的线程(因为是共享模式)。
- 总结:
CountDownLatch
的“等待计数器归零”的核心功能,完全是由 AQS 的共享模式(acquireShared
/releaseShared
)来驱动的。countDown()
调用releaseShared
,await()
调用acquireShared
。
2.4 AQS 与 Semaphore
- 关系:
Semaphore
的内部类Sync
(及其子类NonfairSync
和FairSync
)继承自 AQS。 - 如何工作:
state
变量表示剩余的许可证(permits)数量。tryAcquireShared(int acquires)
:尝试获取指定数量的许可证。如果剩余数量足够,则 CAS 减少state
并返回正数(成功);否则返回负数(失败,入队等待)。tryReleaseShared(int releases)
:释放指定数量的许可证,增加state
,并可能唤醒等待的线程。
- 总结: 信号量的“控制并发访问数量”的功能,是通过 AQS 的共享模式和
state
作为许可证计数器来实现的。
2.5 AQS 与 其他工具
- FutureTask: 也使用了 AQS 来管理任务的执行状态(未开始、运行中、已完成/取消)和等待获取结果的线程。
- CyclicBarrier: 虽然
CyclicBarrier
本身没有直接继承 AQS,但它内部是通过ReentrantLock
和Condition
来实现的,而ReentrantLock
底层就是 AQS。所以它间接依赖 AQS。 - StampedLock:
StampedLock
没有使用 AQS!它使用了更底层的 UNSAFE 操作和复杂的 CLH 队列变种来实现,以追求更高的性能。这是它的一个重要特点。
三、AQS 的两种模式
AQS 定义了两种资源共享方式,子类必须实现相应的方法:
-
Exclusive (独占模式):
- 只有一个线程能执行,如
ReentrantLock
。 - 子类需要实现:
tryAcquire(int)
,tryRelease(int)
。 - 对外调用:
acquire(int)
,release(int)
。
- 只有一个线程能执行,如
-
Share (共享模式):
- 多个线程可同时执行,如
Semaphore
/CountDownLatch
/ReentrantReadWriteLock
的读锁。 - 子类需要实现:
tryAcquireShared(int)
,tryReleaseShared(int)
。 - 对外调用:
acquireShared(int)
,releaseShared(int)
。
- 多个线程可同时执行,如
一个同步器可以同时实现两种模式,比如 ReentrantReadWriteLock
。
四、为什么 AQS 如此重要?
- 统一框架: 为各种不同的同步需求(互斥、共享、计数、信号量)提供了一个统一、高效、可靠的底层实现框架。开发者无需从头实现复杂的线程排队、阻塞、唤醒逻辑。
- 高性能: AQS 的队列管理、CAS 操作、线程阻塞/唤醒机制都经过了高度优化。
- 可靠性: 作为 JDK 的核心组件,经过了无数生产环境的考验。
- 可扩展性: 通过模板方法模式,开发者可以基于 AQS 轻松地创建自己定制的同步组件(虽然大部分情况下 JUC 提供的工具已经足够)。
五、总结:AQS 的“家族树”
AbstractQueuedSynchronizer (AQS)
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
(独占模式实现) / | (共享模式实现) \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
ReentrantLock.Sync ReentrantReadWriteLock.Sync CountDownLatch.Sync Semaphore.Sync
(Fair/Nonfair) (Fair/Nonfair, manages read/write) (manages count) (Fair/Nonfair, manages permits)
简单来说:
ReentrantLock
、ReentrantReadWriteLock
、CountDownLatch
、Semaphore
这些你熟悉的并发工具,它们的“心脏”都是 AQS。- 它们通过继承 AQS 并实现特定的
tryXXX
方法,来定义自己独特的同步语义(锁、读写锁、倒计时、许可证)。 - AQS 负责处理所有复杂的、通用的线程排队、阻塞、唤醒、状态管理的底层细节。