Java并发利器CountDownLatch深度解析与实战应用小结
目录
- Java并发利器:CountDownLatch深度解析与实战应用
- 一、CountDownLatch是什么?
- 1. 基本概念
- 2. 基本用法
- 二、核心API介绍
- 三、经典应用场景
- 场景1:等待多个任务完成
- 场景2:控制并发启动
- 场景3:分段计算
- 四、使用注意事项
- 1. 异常处理要点
- 2. 避免无限等待
- 3. 合理使用线程池
- 五、实际项目案例
- 案例:系统启动初始化
- 六、总结
- 核心特点
- 使用要点
- 适用场景
Java并发利器:CountDownLatch深度解析与实战应用
多线程编程中,让主线程等待所有子任务完成是个常见需求。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); www.devze.com Syspythontem.out.println("用户验证完成"); } catch (InterruptedException e) { e.printStackTrace(); } finally { latch.countDown(); } }).start(); // 支付验证 new Thread(() -> { try { System.out.println("开始支付验证..."); Thread.sleep(800); srYWgfPi 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(newphp 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简洁,概念清晰
- 线程安全:内部实现保证多线程安全
- 灵活应用:适合多种并发协作场景
使用要点
- 异常安全:在finally中调用countDown()
- 超时控制:使用带超时的await()方法
- 一次性使用:CountDownLatch不能重置
- 合理设计:根据实际任务数量设置计数器
适用场景
- 主线程等待多个子任务完成
- 控制多个线程同时开始执行
- 分段并行计算后汇总结果
- 系统启动时的组件初始化
掌握CountDownLatch,让你的多线程程序更加优雅和高效!
到此这篇关于Java并发利器:CountDownLatch深度解析与实战应用的文章就介绍到这了,更多相关Java并发利器:CountDownLatch深度解析与实战应用内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论