写点什么

Java 并发利器:CountDownLatch 深度解析与实战应用

  • 2025-07-02
    福建
  • 本文字数:5470 字

    阅读完需:约 18 分钟

多线程编程中,让主线程等待所有子任务完成是个常见需求。CountDownLatch 就像一个倒计时器,当所有任务完成后,主线程才继续执行。本文将通过简单易懂的方式,带你掌握这个强大的并发工具。


一、CountDownLatch 是什么?


1. 基本概念


CountDownLatch 就是一个"倒计数门闩":

  • 倒计数:从指定数字开始递减到 0

  • 门闩:当计数为 0 时,门闩打开,等待的线程继续执行

  • 一次性:用完即弃,不能重置



2. 基本用法


public class CountDownLatchDemo {    public static void main(String[] args) throws InterruptedException {        // 创建计数器,初始值为3        CountDownLatch latch = new CountDownLatch(3);              // 启动3个任务        for (int i = 0; i < 3; i++) {            final int taskId = i;            new Thread(() -> {                System.out.println("任务" + taskId + "开始执行");                try {                    Thread.sleep(2000); // 模拟任务执行                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println("任务" + taskId + "执行完成");                latch.countDown(); // 计数器减1            }).start();        }              System.out.println("主线程等待所有任务完成...");        latch.await(); // 等待计数器变为0        System.out.println("所有任务完成,主线程继续执行");    }}
复制代码


运行结果:


主线程等待所有任务完成...任务0开始执行任务1开始执行任务2开始执行任务0执行完成任务1执行完成任务2执行完成所有任务完成,主线程继续执行
复制代码


二、核心 API 介绍


CountDownLatch 只有 4 个关键方法:


public class CountDownLatchAPI {    public void demonstrateAPI() throws InterruptedException {        CountDownLatch latch = new CountDownLatch(3);              // 1. countDown() - 计数器减1        latch.countDown();              // 2. await() - 等待计数器变为0        latch.await();              // 3. await(时间, 单位) - 超时等待        boolean finished = latch.await(5, TimeUnit.SECONDS);              // 4. getCount() - 获取当前计数值        long count = latch.getCount();        System.out.println("剩余计数: " + count);    }}
复制代码


三、经典应用场景


场景 1:等待多个任务完成


最常用的场景,主线程等待所有子任务完成:


public class WaitMultipleTasksDemo {      // 模拟订单处理:需要等待库存检查、用户验证、支付验证都完成    public void processOrder(String orderId) throws InterruptedException {        CountDownLatch latch = new CountDownLatch(3);              // 库存检查        new Thread(() -> {            try {                System.out.println("开始库存检查...");                Thread.sleep(1000);                System.out.println("库存检查完成");            } catch (InterruptedException e) {                e.printStackTrace();            } finally {                latch.countDown();            }        }).start();              // 用户验证        new Thread(() -> {            try {                System.out.println("开始用户验证...");                Thread.sleep(1500);                System.out.println("用户验证完成");            } catch (InterruptedException e) {                e.printStackTrace();            } finally {                latch.countDown();            }        }).start();              // 支付验证        new Thread(() -> {            try {                System.out.println("开始支付验证...");                Thread.sleep(800);                System.out.println("支付验证完成");            } catch (InterruptedException e) {                e.printStackTrace();            } finally {                latch.countDown();            }        }).start();              System.out.println("等待所有验证完成...");        latch.await();        System.out.println("订单处理完成: " + orderId);    }}
复制代码


场景 2:控制并发启动


让多个线程同时开始执行:


public class ConcurrentStartDemo {      // 模拟赛跑:所有选手同时起跑    public void startRace() throws InterruptedException {        int runnerCount = 5;        CountDownLatch startGun = new CountDownLatch(1); // 发令枪        CountDownLatch finish = new CountDownLatch(runnerCount); // 终点线              // 创建选手        for (int i = 0; i < runnerCount; i++) {            final int runnerId = i;            new Thread(() -> {                try {                    System.out.println("选手" + runnerId + "准备就绪");                    startGun.await(); // 等待发令枪                                      // 开始跑步                    System.out.println("选手" + runnerId + "开始跑步");                    Thread.sleep(new Random().nextInt(3000)); // 模拟跑步时间                    System.out.println("选手" + runnerId + "到达终点");                                  } catch (InterruptedException e) {                    e.printStackTrace();                } finally {                    finish.countDown();                }            }).start();        }              Thread.sleep(2000); // 等待选手准备        System.out.println("预备...开始!");        startGun.countDown(); // 发令              finish.await(); // 等待所有选手完成        System.out.println("比赛结束!");    }}
复制代码


场景 3:分段计算


将大任务拆分成小任务并行计算:


public class ParallelCalculationDemo {      // 并行计算数组的和    public long calculateSum(int[] array) throws InterruptedException {        int threadCount = 4;        CountDownLatch latch = new CountDownLatch(threadCount);        AtomicLong totalSum = new AtomicLong(0);              int chunkSize = array.length / threadCount;              for (int i = 0; i < threadCount; i++) {            final int start = i * chunkSize;            final int end = (i == threadCount - 1) ? array.length : (i + 1) * chunkSize;                      new Thread(() -> {                long partialSum = 0;                for (int j = start; j < end; j++) {                    partialSum += array[j];                }                totalSum.addAndGet(partialSum);                System.out.println("线程计算范围[" + start + "," + end + "),结果:" + partialSum);                latch.countDown();            }).start();        }              latch.await();        return totalSum.get();    }      public static void main(String[] args) throws InterruptedException {        int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};        ParallelCalculationDemo demo = new ParallelCalculationDemo();        long result = demo.calculateSum(array);        System.out.println("总和:" + result);    }}
复制代码


四、使用注意事项


1. 异常处理要点


核心原则:无论是否异常,都要调用 countDown()


// ✅ 正确写法new Thread(() -> {    try {        // 业务逻辑        doSomething();    } catch (Exception e) {        System.err.println("任务异常:" + e.getMessage());    } finally {        latch.countDown(); // 确保在finally中调用    }}).start();
// ❌ 错误写法new Thread(() -> { try { doSomething(); latch.countDown(); // 异常时不会执行,导致死锁 } catch (Exception e) { System.err.println("任务异常:" + e.getMessage()); // 忘记调用countDown() }}).start();
复制代码


2. 避免无限等待


// 设置超时时间,避免无限等待boolean finished = latch.await(10, TimeUnit.SECONDS);if (finished) {    System.out.println("所有任务完成");} else {    System.out.println("等待超时,可能有任务失败");}
复制代码


3. 合理使用线程池


public void useWithThreadPool() throws InterruptedException {    CountDownLatch latch = new CountDownLatch(5);    ExecutorService executor = Executors.newFixedThreadPool(3);      for (int i = 0; i < 5; i++) {        final int taskId = i;        executor.submit(() -> {            try {                System.out.println("执行任务" + taskId);                Thread.sleep(1000);            } catch (InterruptedException e) {                Thread.currentThread().interrupt();            } finally {                latch.countDown();            }        });    }      latch.await();    executor.shutdown(); // 关闭线程池    System.out.println("所有任务完成");}
复制代码


五、实际项目案例


案例:系统启动初始化


public class SystemInitializer {      public boolean initializeSystem() {        System.out.println("开始系统初始化...");              CountDownLatch latch = new CountDownLatch(4);        AtomicBoolean success = new AtomicBoolean(true);              // 数据库初始化        new Thread(() -> {            try {                System.out.println("初始化数据库连接...");                Thread.sleep(2000);                System.out.println("数据库初始化完成");            } catch (InterruptedException e) {                success.set(false);            } finally {                latch.countDown();            }        }).start();              // Redis初始化        new Thread(() -> {            try {                System.out.println("初始化Redis连接...");                Thread.sleep(1000);                System.out.println("Redis初始化完成");            } catch (InterruptedException e) {                success.set(false);            } finally {                latch.countDown();            }        }).start();              // 配置加载        new Thread(() -> {            try {                System.out.println("加载系统配置...");                Thread.sleep(800);                System.out.println("配置加载完成");            } catch (InterruptedException e) {                success.set(false);            } finally {                latch.countDown();            }        }).start();              // 服务注册        new Thread(() -> {            try {                System.out.println("注册服务...");                Thread.sleep(1500);                System.out.println("服务注册完成");            } catch (InterruptedException e) {                success.set(false);            } finally {                latch.countDown();            }        }).start();              try {            boolean finished = latch.await(10, TimeUnit.SECONDS);            if (finished && success.get()) {                System.out.println("系统初始化成功!");                return true;            } else {                System.out.println("系统初始化失败!");                return false;            }        } catch (InterruptedException e) {            System.out.println("初始化被中断");            return false;        }    }      public static void main(String[] args) {        SystemInitializer initializer = new SystemInitializer();        initializer.initializeSystem();    }}
复制代码


六、总结


CountDownLatch 是 Java 并发编程中的实用工具,它的核心价值在于:

🎯 核心特点

  • 简单易用:API 简洁,概念清晰

  • 线程安全:内部实现保证多线程安全

  • 灵活应用:适合多种并发协作场景


📝 使用要点

  1. 异常安全:在 finally 中调用 countDown()

  2. 超时控制:使用带超时的 await()方法

  3. 一次性使用:CountDownLatch 不能重置

  4. 合理设计:根据实际任务数量设置计数器


🚀 适用场景

  • 主线程等待多个子任务完成

  • 控制多个线程同时开始执行

  • 分段并行计算后汇总结果

  • 系统启动时的组件初始化


掌握 CountDownLatch,让你的多线程程序更加优雅和高效!


文章转载自:大毛啊

原文链接:https://www.cnblogs.com/damaoa/p/18932748

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2025-04-01 加入

还未添加个人简介

评论

发布
暂无评论
Java并发利器:CountDownLatch深度解析与实战应用_Java_量贩潮汐·WholesaleTide_InfoQ写作社区