导读:本期聚焦于小伙伴创作的《如何通过 AQS 的 Shared 模式底层实现理解 CountDownLatch 在任务对齐中的同步原理》,敬请观看详情。当一个主线程需要等待多个子任务全部跑完才能继续,若用 Thread.join 逐个阻塞会带来资源浪费与超时失控。CountDownLatch 借助 AQS 的共享模式,用同一个同步状态记录未完成任务数,所有等待线程进入共享队列,计数归零后一次性放行。理解其底层 acquireShared 与 releaseShared 的链路,能看清它是如何在高并发下完成任务对齐而不发生漏唤醒的。

CountDownLatch 是 Java 并发包里典型的同步辅助类,它的核心用途是让一个或多个线程等待其他线程完成一组操作。很多人只停留在 countDown 和 await 的 API 表面,却不清楚它为什么能在大批量任务对齐场景中保持正确性与高性能。其实它的全部能力都建立在 AbstractQueuedSynchronizer(AQS)的 Shared 模式之上。

如何通过 AQS 的 Shared 模式底层实现理解 CountDownLatch 在任务对齐中的同步原理

一、AQS Shared 模式的基本机制

AQS 内部维护了一个 volatile 修饰的同步状态 state 以及一个 CLH 变体的等待队列。与 Exclusive 模式不同,Shared 模式允许多个线程同时获取资源。在 Shared 模式下,线程通过 acquireShared 方法尝试获取同步状态,如果返回值大于等于 0 表示获取成功,否则线程会被包装成节点加入队列并挂起。

当某个线程调用 releaseShared 释放资源时,AQS 会修改 state 并尝试唤醒队列中的后续节点。关键点在于,Shared 模式的唤醒过程使用了传播机制:如果一个节点被唤醒并成功获取共享资源,它会继续尝试唤醒下一个共享节点,从而保证在 state 满足条件时,所有等待线程都能被放行,而不会出现只唤醒一个的遗漏情况。

// AQS 中共享模式释放的典型逻辑片段(简化)
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

二、CountDownLatch 对 AQS 的 Shared 实现

CountDownLatch 内部有一个私有的 Sync 类继承自 AQS,并将构造时传入的计数作为初始 state。tryAcquireShared 方法判断 state 是否为 0,只有为 0 时才返回 1,否则返回 -1,这表示只要计数没归零,调用 await 的线程就必须排队等待。

tryReleaseShared 则每次将 state 减 1,并使用 CAS 保证线程安全。当减到 0 时返回 true,触发 AQS 的 doReleaseShared 唤醒流程。由于是 Shared 模式,所有在队列里等待的线程会被连续唤醒,从而达成“任务全部完成,主流程统一继续”的对齐效果。

// CountDownLatch 内部 Sync 的核心实现(简化)
private static final class Sync extends AbstractQueuedSynchronizer {
    Sync(int count) {
        setState(count);
    }

    protected int tryAcquireShared(int acquires) {
        // 状态为 0 才能获取,否则进入等待
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int c = getState();
            if (c == 0) {
                return false;
            }
            int nextc = c - 1;
            if (compareAndSetState(c, nextc)) {
                return nextc == 0;
            }
        }
    }
}

三、任务对齐中的同步原理剖析

所谓任务对齐,是指主线程必须等 N 个并行子任务都执行到某个边界点后再统一推进。使用 CountDownLatch 时,主线程调用 await 进入 AQS 共享队列;每个子任务结束前调用 countDown,使 state 递减。由于 state 的递减和可见性由 AQS 的 volatile 与 CAS 保障,因此即使高并发提交任务,计数也不会错乱。

当最后一个子任务调用 countDown 将 state 置为 0,tryReleaseShared 返回 true,AQS 开始共享传播唤醒。此时队列中所有的 await 线程会依次被 unpark,并重新检查 tryAcquireShared,发现 state 为 0 后全部放行。这种“一次性全放行”的特性,正是 Shared 模式相对于 Exclusive 模式在任务对齐上的最大优势,也避免了用循环轮询或多次加锁带来的开销。

四、与其他方案的对比及使用注意

若使用 Thread.join,主线程必须顺序等待每个子线程结束,无法真正并行对齐;若用 CyclicBarrier,则侧重于多线程互相等待再同时出发,且可重用,而 CountDownLatch 不可重置,更适合一次性任务对齐。理解它们底层分别是 AQS Shared 或独占配合条件队列,有助于在设计中正确选型。

在实际使用中,要避免在 countDown 前抛出未捕获异常导致计数不归零,从而使 await 线程永久挂起。推荐将 countDown 放在 finally 块中,或结合 await 的超时重载方法保护系统。通过掌握 AQS Shared 的底层链路,我们不仅能用好 CountDownLatch,也能更快理解 Semaphore 等同类工具的运转逻辑。

// 安全的任务对齐示例
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            // 模拟子任务
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            latch.countDown();
        }
    }).start();
}
latch.await(); // 主线程等待三个任务全部结束

AQSCountDownLatchshared_mode修改时间:2026-08-01 12:15:27

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。