找回密码
 立即注册
首页 业界区 业界 Java并发利器:CountDownLatch深度解析与实战应用 ...

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

枢覆引 2025-6-17 10:25:37
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. 基本用法
  1. public class CountDownLatchDemo {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         // 创建计数器,初始值为3
  4.         CountDownLatch latch = new CountDownLatch(3);
  5.       
  6.         // 启动3个任务
  7.         for (int i = 0; i < 3; i++) {
  8.             final int taskId = i;
  9.             new Thread(() -> {
  10.                 System.out.println("任务" + taskId + "开始执行");
  11.                 try {
  12.                     Thread.sleep(2000); // 模拟任务执行
  13.                 } catch (InterruptedException e) {
  14.                     e.printStackTrace();
  15.                 }
  16.                 System.out.println("任务" + taskId + "执行完成");
  17.                 latch.countDown(); // 计数器减1
  18.             }).start();
  19.         }
  20.       
  21.         System.out.println("主线程等待所有任务完成...");
  22.         latch.await(); // 等待计数器变为0
  23.         System.out.println("所有任务完成,主线程继续执行");
  24.     }
  25. }
复制代码
运行结果:
  1. 主线程等待所有任务完成...
  2. 任务0开始执行
  3. 任务1开始执行
  4. 任务2开始执行
  5. 任务0执行完成
  6. 任务1执行完成
  7. 任务2执行完成
  8. 所有任务完成,主线程继续执行
复制代码
二、核心API介绍

CountDownLatch只有4个关键方法:
  1. public class CountDownLatchAPI {
  2.     public void demonstrateAPI() throws InterruptedException {
  3.         CountDownLatch latch = new CountDownLatch(3);
  4.       
  5.         // 1. countDown() - 计数器减1
  6.         latch.countDown();
  7.       
  8.         // 2. await() - 等待计数器变为0
  9.         latch.await();
  10.       
  11.         // 3. await(时间, 单位) - 超时等待
  12.         boolean finished = latch.await(5, TimeUnit.SECONDS);
  13.       
  14.         // 4. getCount() - 获取当前计数值
  15.         long count = latch.getCount();
  16.         System.out.println("剩余计数: " + count);
  17.     }
  18. }
复制代码
三、经典应用场景

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

最常用的场景,主线程等待所有子任务完成:
  1. public class WaitMultipleTasksDemo {
  2.   
  3.     // 模拟订单处理:需要等待库存检查、用户验证、支付验证都完成
  4.     public void processOrder(String orderId) throws InterruptedException {
  5.         CountDownLatch latch = new CountDownLatch(3);
  6.       
  7.         // 库存检查
  8.         new Thread(() -> {
  9.             try {
  10.                 System.out.println("开始库存检查...");
  11.                 Thread.sleep(1000);
  12.                 System.out.println("库存检查完成");
  13.             } catch (InterruptedException e) {
  14.                 e.printStackTrace();
  15.             } finally {
  16.                 latch.countDown();
  17.             }
  18.         }).start();
  19.       
  20.         // 用户验证
  21.         new Thread(() -> {
  22.             try {
  23.                 System.out.println("开始用户验证...");
  24.                 Thread.sleep(1500);
  25.                 System.out.println("用户验证完成");
  26.             } catch (InterruptedException e) {
  27.                 e.printStackTrace();
  28.             } finally {
  29.                 latch.countDown();
  30.             }
  31.         }).start();
  32.       
  33.         // 支付验证
  34.         new Thread(() -> {
  35.             try {
  36.                 System.out.println("开始支付验证...");
  37.                 Thread.sleep(800);
  38.                 System.out.println("支付验证完成");
  39.             } catch (InterruptedException e) {
  40.                 e.printStackTrace();
  41.             } finally {
  42.                 latch.countDown();
  43.             }
  44.         }).start();
  45.       
  46.         System.out.println("等待所有验证完成...");
  47.         latch.await();
  48.         System.out.println("订单处理完成: " + orderId);
  49.     }
  50. }
复制代码
场景2:控制并发启动

让多个线程同时开始执行:
  1. public class ConcurrentStartDemo {
  2.   
  3.     // 模拟赛跑:所有选手同时起跑
  4.     public void startRace() throws InterruptedException {
  5.         int runnerCount = 5;
  6.         CountDownLatch startGun = new CountDownLatch(1); // 发令枪
  7.         CountDownLatch finish = new CountDownLatch(runnerCount); // 终点线
  8.       
  9.         // 创建选手
  10.         for (int i = 0; i < runnerCount; i++) {
  11.             final int runnerId = i;
  12.             new Thread(() -> {
  13.                 try {
  14.                     System.out.println("选手" + runnerId + "准备就绪");
  15.                     startGun.await(); // 等待发令枪
  16.                   
  17.                     // 开始跑步
  18.                     System.out.println("选手" + runnerId + "开始跑步");
  19.                     Thread.sleep(new Random().nextInt(3000)); // 模拟跑步时间
  20.                     System.out.println("选手" + runnerId + "到达终点");
  21.                   
  22.                 } catch (InterruptedException e) {
  23.                     e.printStackTrace();
  24.                 } finally {
  25.                     finish.countDown();
  26.                 }
  27.             }).start();
  28.         }
  29.       
  30.         Thread.sleep(2000); // 等待选手准备
  31.         System.out.println("预备...开始!");
  32.         startGun.countDown(); // 发令
  33.       
  34.         finish.await(); // 等待所有选手完成
  35.         System.out.println("比赛结束!");
  36.     }
  37. }
复制代码
场景3:分段计算

将大任务拆分成小任务并行计算:
  1. public class ParallelCalculationDemo {
  2.   
  3.     // 并行计算数组的和
  4.     public long calculateSum(int[] array) throws InterruptedException {
  5.         int threadCount = 4;
  6.         CountDownLatch latch = new CountDownLatch(threadCount);
  7.         AtomicLong totalSum = new AtomicLong(0);
  8.       
  9.         int chunkSize = array.length / threadCount;
  10.       
  11.         for (int i = 0; i < threadCount; i++) {
  12.             final int start = i * chunkSize;
  13.             final int end = (i == threadCount - 1) ? array.length : (i + 1) * chunkSize;
  14.          
  15.             new Thread(() -> {
  16.                 long partialSum = 0;
  17.                 for (int j = start; j < end; j++) {
  18.                     partialSum += array[j];
  19.                 }
  20.                 totalSum.addAndGet(partialSum);
  21.                 System.out.println("线程计算范围[" + start + "," + end + "),结果:" + partialSum);
  22.                 latch.countDown();
  23.             }).start();
  24.         }
  25.       
  26.         latch.await();
  27.         return totalSum.get();
  28.     }
  29.   
  30.     public static void main(String[] args) throws InterruptedException {
  31.         int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  32.         ParallelCalculationDemo demo = new ParallelCalculationDemo();
  33.         long result = demo.calculateSum(array);
  34.         System.out.println("总和:" + result);
  35.     }
  36. }
复制代码
四、使用注意事项

1. 异常处理要点

核心原则:无论是否异常,都要调用countDown()
  1. // ✅ 正确写法
  2. new Thread(() -> {
  3.     try {
  4.         // 业务逻辑
  5.         doSomething();
  6.     } catch (Exception e) {
  7.         System.err.println("任务异常:" + e.getMessage());
  8.     } finally {
  9.         latch.countDown(); // 确保在finally中调用
  10.     }
  11. }).start();
  12. // ❌ 错误写法
  13. new Thread(() -> {
  14.     try {
  15.         doSomething();
  16.         latch.countDown(); // 异常时不会执行,导致死锁
  17.     } catch (Exception e) {
  18.         System.err.println("任务异常:" + e.getMessage());
  19.         // 忘记调用countDown()
  20.     }
  21. }).start();
复制代码
2. 避免无限等待
  1. // 设置超时时间,避免无限等待
  2. boolean finished = latch.await(10, TimeUnit.SECONDS);
  3. if (finished) {
  4.     System.out.println("所有任务完成");
  5. } else {
  6.     System.out.println("等待超时,可能有任务失败");
  7. }
复制代码
3. 合理使用线程池
  1. public void useWithThreadPool() throws InterruptedException {
  2.     CountDownLatch latch = new CountDownLatch(5);
  3.     ExecutorService executor = Executors.newFixedThreadPool(3);
  4.   
  5.     for (int i = 0; i < 5; i++) {
  6.         final int taskId = i;
  7.         executor.submit(() -> {
  8.             try {
  9.                 System.out.println("执行任务" + taskId);
  10.                 Thread.sleep(1000);
  11.             } catch (InterruptedException e) {
  12.                 Thread.currentThread().interrupt();
  13.             } finally {
  14.                 latch.countDown();
  15.             }
  16.         });
  17.     }
  18.   
  19.     latch.await();
  20.     executor.shutdown(); // 关闭线程池
  21.     System.out.println("所有任务完成");
  22. }
复制代码
五、实际项目案例

案例:系统启动初始化
  1. public class SystemInitializer {
  2.   
  3.     public boolean initializeSystem() {
  4.         System.out.println("开始系统初始化...");
  5.       
  6.         CountDownLatch latch = new CountDownLatch(4);
  7.         AtomicBoolean success = new AtomicBoolean(true);
  8.       
  9.         // 数据库初始化
  10.         new Thread(() -> {
  11.             try {
  12.                 System.out.println("初始化数据库连接...");
  13.                 Thread.sleep(2000);
  14.                 System.out.println("数据库初始化完成");
  15.             } catch (InterruptedException e) {
  16.                 success.set(false);
  17.             } finally {
  18.                 latch.countDown();
  19.             }
  20.         }).start();
  21.       
  22.         // Redis初始化
  23.         new Thread(() -> {
  24.             try {
  25.                 System.out.println("初始化Redis连接...");
  26.                 Thread.sleep(1000);
  27.                 System.out.println("Redis初始化完成");
  28.             } catch (InterruptedException e) {
  29.                 success.set(false);
  30.             } finally {
  31.                 latch.countDown();
  32.             }
  33.         }).start();
  34.       
  35.         // 配置加载
  36.         new Thread(() -> {
  37.             try {
  38.                 System.out.println("加载系统配置...");
  39.                 Thread.sleep(800);
  40.                 System.out.println("配置加载完成");
  41.             } catch (InterruptedException e) {
  42.                 success.set(false);
  43.             } finally {
  44.                 latch.countDown();
  45.             }
  46.         }).start();
  47.       
  48.         // 服务注册
  49.         new Thread(() -> {
  50.             try {
  51.                 System.out.println("注册服务...");
  52.                 Thread.sleep(1500);
  53.                 System.out.println("服务注册完成");
  54.             } catch (InterruptedException e) {
  55.                 success.set(false);
  56.             } finally {
  57.                 latch.countDown();
  58.             }
  59.         }).start();
  60.       
  61.         try {
  62.             boolean finished = latch.await(10, TimeUnit.SECONDS);
  63.             if (finished && success.get()) {
  64.                 System.out.println("系统初始化成功!");
  65.                 return true;
  66.             } else {
  67.                 System.out.println("系统初始化失败!");
  68.                 return false;
  69.             }
  70.         } catch (InterruptedException e) {
  71.             System.out.println("初始化被中断");
  72.             return false;
  73.         }
  74.     }
  75.   
  76.     public static void main(String[] args) {
  77.         SystemInitializer initializer = new SystemInitializer();
  78.         initializer.initializeSystem();
  79.     }
  80. }
复制代码
六、总结

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

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册