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并发编程中的实用工具,它的核心价值在于:
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |