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并发编程、性能优化等技术干货,一起在技术路上精进成长!

Java并发利器:CountDownLatch深度解析与实战应用的更多相关文章

  1. Java 8 Optional 类深度解析

    Java 8 Optional 类深度解析 身为一名Java程序员,大家可能都有这样的经历:调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法.我们首先要判断这个返回值是否为null,只 ...

  2. java并发初探CountDownLatch

    java并发初探CountDownLatch CountDownLatch是同步工具类能够允许一个或者多个线程等待直到其他线程完成操作. 当前前程A调用CountDownLatch的await方法进入 ...

  3. java并发中CountDownLatch的使用

    文章目录 主线程等待子线程全都结束之后再开始运行 等待所有线程都准备好再一起执行 停止CountdownLatch的await java并发中CountDownLatch的使用 在java并发中,控制 ...

  4. Java 并发:volatile 关键字解析

    摘要: 在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保 ...

  5. Java并发编程 Volatile关键字解析

    volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了 ...

  6. Java并发——ThreadPoolExecutor线程池解析及Executor创建线程常见四种方式

    前言: 在刚学Java并发的时候基本上第一个demo都会写new Thread来创建线程.但是随着学的深入之后发现基本上都是使用线程池来直接获取线程.那么为什么会有这样的情况发生呢? new Thre ...

  7. 【Java 并发003】原理层面:Java并发三特性全解析

    一.前言 不管什么语言,并发的编程都是在高级的部分,因为并发的涉及的知识太广,不单单是操作系统的知识,还有计算机的组成的知识等等.说到底,这些年硬件的不断的发展,但是一直有一个核心的矛盾在:CPU.内 ...

  8. JAVA并发,CountDownLatch使用

    该文章转自:http://www.itzhai.com/the-introduction-and-use-of-a-countdownlatch.html CountDownLatch 1.类介绍 一 ...

  9. java并发系列(六)-----Java并发:volatile关键字解析

    在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...

  10. Goroutine并发调度模型深度解析之手撸一个协程池

    golanggoroutine协程池Groutine Pool高并发 并发(并行),一直以来都是一个编程语言里的核心主题之一,也是被开发者关注最多的话题:Go语言作为一个出道以来就自带 『高并发』光环 ...

随机推荐

  1. linux命令:lsof命令

    lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件.所以如传输控制协议 ...

  2. Win环境下的批处理命令和JScript脚本结合使用笔记

    最近工作有接触到.bat 批处理命令,在Win环境下编写的时候基于以前的编码习惯,觉得批处理语法可读性较差,于是学习了解了一下结合JScript的用法,特此记录. 什么是JScript JScript ...

  3. Oracle impdp 导入报错 ORA-39083 + ORA-00439

    Oracle 11G R2 impdp导入的时候 一直报错: ORA-39083: 对象类型 TABLE:"xxx"."xxx" 创建失败, 出现错误: ORA ...

  4. LeetCode1464. 数组中两元素的最大乘积-JAVA

    题目 给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使 (nums[i]-1)*(nums[j]-1) 取得最大值.请你计算并返回该式的最大值. 示例 1: 输入:nums = ...

  5. Greenplum数据库时间操作汇总

    Greenplum数据库时间操作与mysql有一些区别,汇总以往笔记记录下来. greenplum时间格式:'yyyy-mm-dd hh24:mi:ss.us'.'yyyy-mm-dd hh:mi:s ...

  6. apk签名问题

    https://www.jianshu.com/p/0bd7b6d6e068 https://blog.51cto.com/u_15520037/5703487

  7. 可横竖控制的Text bg Control

    using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; [RequireComponent(typeof(Co ...

  8. Spring RestTemplate使用方法总结

    1. 引入依赖 首先,需要确认项目中是否直接或者间接引入过spring-web依赖,如果没有引入过,需要在pom.xml中添加以下代码引入依赖: <dependency> <grou ...

  9. Eclipse 中 JAVA AWT相关包不提示问题(解决)

    原因: 由于在2021年7月15日 OpenJDK管理委员会全票通过批准成立由Phil Race担任初始负责人的 Client Libraries Group(客户端类库工作组). 新的工作组将继续赞 ...

  10. 基于Surprise和Flask构建个性化电影推荐系统:从算法到全栈实现

    一.引言:推荐系统的魔法与现实意义 在Netflix每年节省10亿美元内容采购成本的背后,在YouTube占据用户80%观看时长的推荐算法中,推荐系统正悄然改变内容消费模式.本文将带您从零开始构建一个 ...