怎么利用 CountDownLatch 实现多线程任务的同步计数

来源:苹果APP网作者:高永康头衔:资深程序员
导读:本期聚焦于高永康创作的《怎么利用 CountDownLatch 实现多线程任务的同步计数》,敬请观看详情。等待一组线程全部完成后再继续往下执行,这是并发编程里非常典型的需求。CountDownLatch 通过一个简单的计数器配合 await 与 countDown 两个核心方法,就能优雅地解决这个问题。本文将详细讲解 CountDownLatch 的内部实现原理,包括基于 AQS 共享模式的状态控制逻辑,并结合主线程汇总任务结果、并发请求合并返回、并行数据初始化等常见场景给出完整可运行的代码示例,同时分析它与 CyclicBarrier、join 方法的差异以及使用中容易踩到的坑,帮助你真正掌握这个并发工具。

在并发编程中,我们经常遇到这样的场景:主线程把一个大任务拆分成若干子任务交给线程池执行,需要等所有子任务都跑完之后再汇总结果;或者一个服务启动时,需要等待配置加载、缓存预热、资源初始化等多个前置动作全部就绪后才能对外提供服务。这类需求本质上就是一个同步计数问题,而 JDK 中提供的 CountDownLatch 正是为此而生的利器。它结构简单、语义清晰,是 Java 并发工具包里使用频率最高的组件之一。

怎么利用 CountDownLatch 实现多线程任务的同步计数

CountDownLatch 的核心概念与工作原理

CountDownLatch 直译过来就是倒计时锁存器,它的内部维护了一个整数计数器。初始化时传入一个数值 N,每调用一次 countDown 方法计数器就减一,而调用 await 方法的线程会被阻塞,直到计数器归零才能继续执行。可以把它想象成一道闸门:N 个人每人都握着一个开关,只有所有人都按下了开关(计数归零),闸门才会打开。

从源码角度看,CountDownLatch 的实现完全依赖于 AbstractQueuedSynchronizer(AQS)。它把 AQS 的 state 变量当作计数器使用:构造时通过 setState 设置初始值,countDown 内部调用的是 AQS 的 releaseShared 操作,利用 CAS 把 state 减一,减到零时会唤醒所有在同步队列中等待的线程;await 则调用 acquireSharedInterruptibly,state 不为零就把当前线程挂起排队。理解了这一点,你就能明白为什么 CountDownLatch 的行为是这样设计的。

需要特别注意的是,CountDownLatch 是一次性的。计数器一旦归零就永远保持为零,这个锁存器无法被重置复用。如果业务中需要循环多次使用屏障,应该考虑 CyclicBarrier 而不是 CountDownLatch。

典型使用场景与完整代码示例

最常见的用法是主线程等待多个工作线程完成汇总。下面给出一个完整的例子:假设要统计一个目录下三类文件的行数,主线程等待三个统计线程全部完成后输出总数。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class LineCountDemo {

    public static void main(String[] args) throws InterruptedException {
        // 计数器初始值为 3,对应 3 个子任务
        CountDownLatch latch = new CountDownLatch(3);
        // 用一个数组收集结果,主线程最后汇总
        long[] results = new long[3];

        ExecutorService pool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 3; i++) {
            final int index = i;
            pool.submit(() -> {
                try {
                    // 模拟每类文件的行数统计,实际中替换为真实逻辑
                    results[index] = doCount(index);
                } finally {
                    // 必须放在 finally 中,防止异常导致计数不减
                    latch.countDown();
                }
            });
        }

        // 主线程阻塞等待,直到计数归零
        latch.await();
        pool.shutdown();

        long total = results[0] + results[1] + results[2];
        System.out.println("三类文件总行数: " + total);
    }

    private static long doCount(int type) {
        try {
            Thread.sleep(1000); // 模拟耗时操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return 100L * (type + 1);
    }
}

上面这段代码有几个值得强调的细节。第一,countDown 必须放在 finally 块中,否则一旦子任务抛出异常,计数器永远无法归零,主线程会一直卡死在 await 上,这是生产事故的高发点。第二,await 支持带超时的重载版本 await(long timeout, TimeUnit unit),建议在对外服务中使用它,避免某个子任务卡住拖垮整个请求线程。第三,汇总结果用的是普通数组,因为每个线程写入不同的下标,不存在竞争;如果多个线程要写同一个共享变量,就需要改用 AtomicInteger 或者加锁。

另一个典型场景是服务启动时的资源初始化,或者接口聚合:一个 BFF 接口需要同时调用三个下游服务再合并返回,用 CountDownLatch 加线程池可以把串行调用改成并行,整体耗时从三个接口之和降到最慢的那个接口的耗时,效果非常明显。

CountDownLatch 与 join、CyclicBarrier 的对比

Thread 的 join 方法也能实现等待,但它有明显的局限:join 只能等待线程对象本身结束,如果任务提交给了线程池,你拿不到也管理不了线程对象,join 就无从谈起。而 CountDownLatch 等待的是一个事件信号,不管这个计数是由谁在哪个线程里递减的,解耦程度更高。

与 CyclicBarrier 相比,两者的区别主要在语义和复用性上。CyclicBarrier 强调的是所有线程互相等待,到齐后一起继续,并且可以通过 reset 重置循环使用;CountDownLatch 强调的是一方等待多方完成,且不可重置。另外 CyclicBarrier 还支持在屏障打开时执行一个回调动作,适合做阶段性的批处理。简单记忆:等待别人干活用 CountDownLatch,大家互相等齐再走用 CyclicBarrier。

使用时还有几个坑要避开。一是计数器初始值与实际 countDown 次数必须严格一致,多减一次虽然不报错但会破坏语义,少减一次则会永久阻塞;二是 await 响应中断,如果等待线程被中断会抛出 InterruptedException,需要妥善处理中断状态;三是不要在持有锁的情况下调用 await,容易与其他线程的 countDown 形成死锁。掌握这些原则,CountDownLatch 就能成为你手里可靠的并发协调工具。

CountDownLatch多线程同步Java并发编程修改时间:2026-09-16 11:48:33

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