AQS(AbstractQueuedSynchronizer)是Java并发包中构建锁和同步器的核心框架。它依赖于一个整型的原子变量state来管理同步状态,并使用CLH队列来管理等待线程。理解AQS在共享模式下的状态传播机制,是区分像CountDownLatch(闭锁)和Semaphore(信号量)这类同步器行为的关键。
1. AQS共享模式基础
AQS支持独占模式(如ReentrantLock)和共享模式(如CountDownLatch和Semaphore)。在共享模式下,多个线程可以同时获取同步状态。释放状态时,AQS需要确保通知到所有或部分等待的线程,这就是“状态传播”的核心。
当同步器释放状态时,都会调用AQS的模板方法releaseShared(int arg)。该方法最终会触发doReleaseShared()逻辑,负责唤醒等待队列中的节点。
2. CountDownLatch(闭锁)的传播机制:一次性爆发
CountDownLatch的核心用途是等待其他线程完成一组操作。它初始化一个正数状态,当状态减到零时,闭锁被打开,所有等待的线程都会被释放。
状态实现:
CountDownLatch的tryAcquireShared会检查state == 0。只要不为零,获取就失败。其关键在于tryReleaseShared:
// CountDownLatch 内部 Sync 类的 tryReleaseShared 逻辑
protected int tryReleaseShared(int releaseCount) {
// 忽略 releaseCount,只关注状态减到 0
for (;;) {
int c = getState();
if (c == 0) return 0; // 已经打开了
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
// 如果 state 成功减到 0,返回 1,表示需要唤醒等待线程
return nextc == 0 ? 1 : 0;
}
}
}
当state减到0时,tryReleaseShared返回1(表示成功释放),这会触发AQS的doReleaseShared方法。由于闭锁的性质,所有等待线程都应该被唤醒。
传播特性: 闭锁的释放是一次性爆发的,一旦状态达到终点(0),信号就会在队列中持续传播,直到队列末尾,确保所有等待的线程都被唤醒并退出等待。
3. Semaphore(信号量)的传播机制:按需逐个唤醒
Semaphore用于控制同时访问某个资源的线程数量(即许可证数量)。释放一个信号量意味着增加一个许可证。
状态实现:
Semaphore的tryAcquireShared尝试原子性地减少状态(获取许可)。其tryReleaseShared尝试原子性地增加状态:
// Semaphore 内部 Sync 类的 tryReleaseShared 逻辑
protected int tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
// 检查溢出,但通常忽略
if (compareAndSetState(current, next)) {
// 成功释放,返回 1,表示需要唤醒等待线程
return 1;
}
}
}
Semaphore每次成功释放许可证后,tryReleaseShared都会返回1,这意味着它需要唤醒队列中的线程。然而,Semaphore的唤醒通常是按需逐个唤醒。
当Semaphore释放一个许可证并唤醒队头线程A后,线程A获取许可证,并继续执行。如果队列中还有其他等待线程B,除非Semaphore在短时间内释放了多个许可证,否则信号不会立即传播给线程B。
4. 传播机制的核心差异:doReleaseShared中的级联传播
虽然两者都调用doReleaseShared,但行为差异主要体现在线程被唤醒后的动作:
- CountDownLatch: 当队头线程被唤醒后(因为state=0),它成功获取状态,但不需要消耗状态。它接着检查队列的下一个节点,并继续触发doReleaseShared,将信号传递下去。这导致信号像链式反应一样快速传播。
-
Semaphore: 当队头线程被唤醒后,它成功获取许可(消耗state)。由于它消耗了状态,通常情况下,它不会主动触发信号继续向队列后方传播。只有在以下两种情况下信号才会继续传播:
- 释放操作一次性增加了多个许可证(状态增加足够多)。
- 被唤醒的线程发现当前状态仍有足够的许可证,并且决定继续尝试唤醒后继节点(这是acquireShared成功后的一个逻辑分支,但在Semaphore中并不常用)。
结论:
CountDownLatch的释放代表状态的终结(State=0),信号在AQS队列中必须完全级联传播,因为所有等待者都应该被释放。
Semaphore的释放代表资源的增加(State++),信号通常只唤醒一个线程,该线程消耗资源。只有当资源富余到足够释放下一个等待线程时,信号才会在队列中传播,但这种传播效率远低于CountDownLatch的“一次性开放”模式,它更接近于按需竞争。
汤不热吧