欢迎光临
我们一直在努力

怎样理解 AQS 队列同步器的状态传播机制:信号量与闭锁的底层区别

AQS(AbstractQueuedSynchronizer)是Java并发包中构建锁和同步器的核心框架。它依赖于一个整型的原子变量state来管理同步状态,并使用CLH队列来管理等待线程。理解AQS在共享模式下的状态传播机制,是区分像CountDownLatch(闭锁)和Semaphore(信号量)这类同步器行为的关键。

1. AQS共享模式基础

AQS支持独占模式(如ReentrantLock)和共享模式(如CountDownLatchSemaphore)。在共享模式下,多个线程可以同时获取同步状态。释放状态时,AQS需要确保通知到所有或部分等待的线程,这就是“状态传播”的核心。

当同步器释放状态时,都会调用AQS的模板方法releaseShared(int arg)。该方法最终会触发doReleaseShared()逻辑,负责唤醒等待队列中的节点。

2. CountDownLatch(闭锁)的传播机制:一次性爆发

CountDownLatch的核心用途是等待其他线程完成一组操作。它初始化一个正数状态,当状态减到零时,闭锁被打开,所有等待的线程都会被释放。

状态实现

CountDownLatchtryAcquireShared会检查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用于控制同时访问某个资源的线程数量(即许可证数量)。释放一个信号量意味着增加一个许可证。

状态实现

SemaphoretryAcquireShared尝试原子性地减少状态(获取许可)。其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,但行为差异主要体现在线程被唤醒后的动作:

  1. CountDownLatch: 当队头线程被唤醒后(因为state=0),它成功获取状态,但不需要消耗状态。它接着检查队列的下一个节点,并继续触发doReleaseShared,将信号传递下去。这导致信号像链式反应一样快速传播。

  2. Semaphore: 当队头线程被唤醒后,它成功获取许可(消耗state)。由于它消耗了状态,通常情况下,它不会主动触发信号继续向队列后方传播。只有在以下两种情况下信号才会继续传播:

    • 释放操作一次性增加了多个许可证(状态增加足够多)。
    • 被唤醒的线程发现当前状态仍有足够的许可证,并且决定继续尝试唤醒后继节点(这是acquireShared成功后的一个逻辑分支,但在Semaphore中并不常用)。

结论:

CountDownLatch的释放代表状态的终结(State=0),信号在AQS队列中必须完全级联传播,因为所有等待者都应该被释放。

Semaphore的释放代表资源的增加(State++),信号通常只唤醒一个线程,该线程消耗资源。只有当资源富余到足够释放下一个等待线程时,信号才会在队列中传播,但这种传播效率远低于CountDownLatch的“一次性开放”模式,它更接近于按需竞争

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » 怎样理解 AQS 队列同步器的状态传播机制:信号量与闭锁的底层区别
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址