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

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

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

一、CountDownLatch是什么?

1. 基本概念

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

  • 倒计数:从指定数字开始递减到0
  • 门闩:当计数为0时,门闩打开,等待的线程继续执行
  • 一次性:用完即弃,不能重置
graph TD A[创建CountDownLatch 3] --> B[启动3个任务] B --> C[任务1完成 countDown] B --> D[任务2完成 countDown] B --> E[任务3完成 countDown] C --> F{计数器=0?} D --> F E --> F F -->|是| G[主线程继续执行] F -->|否| H[继续等待]

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,让你的多线程程序更加优雅和高效!


觉得文章有用?欢迎关注我的微信公众号【一只划水的程序猿】,持续分享Java并发编程、性能优化等技术干货,一起在技术路上精进成长!

发表评论

评论已关闭。

相关文章