Java CountDownLatch闭锁完全指南:从概念到源码

📅 2026/6/18 17:31:23
Java CountDownLatch闭锁完全指南:从概念到源码
Java CountDownLatch闭锁完全指南从概念到源码一、 CountDownLatch概述什么是闭锁1.1 官方定义1.2 核心特性二、 流程图CountDownLatch核心工作流程三、 CountDownLatch核心方法详解3.1 构造方法3.2 await() —— 等待计数器归零3.3 countDown() —— 计数器减13.4 其他常用方法四、 流程图CountDownLatch的两种经典用法4.1 用法一启动信号一次触发多线程同时启动4.2 用法二完成信号等待所有任务完成4.3 CountDownLatch vs Thread.join()五、 底层原理AQS共享模式5.1 核心架构5.2 await() 的底层流程5.3 countDown() 的底层流程六、 避坑指南6.1 常见陷阱6.2 最佳实践清单The Begin点点关注收藏不迷路⬇ ⬇ 底部 ⬇ ⬇本文导读本文将系统讲解Java中CountDownLatch闭锁的核心概念、工作原理和使用方法从倒计时门闩的生动比喻到AQS的底层实现并通过大量实战示例帮助你掌握这个经典的并发同步工具。全文包含五大核心章节、3个彩色流程图、9个代码示例。预计阅读时间25分钟。一、 CountDownLatch概述什么是闭锁1.1 官方定义根据Oracle官方文档的定义CountDownLatch是一个同步辅助工具它允许一个或多个线程等待直到在其他线程中执行的一组操作完成。形象比喻CountDownLatch就像一道倒计时门闩Latch。门闩上有一个计数器初始值为N。每当一个线程完成了自己的任务就拨动一次门闩计数器减1。当计数器归零时门闩啪的一声打开所有被挡在门外的等待线程一拥而入。1.2 核心特性特性说明计数器不可重置一旦计数器归零就永久保持开放状态无法重置一次性使用这是一个one-shot现象——计数不能被重置等待灵活调用countDown()的线程不需要等待计数器归零就可以继续执行底层基于AQS使用AQS的共享模式实现与CyclicBarrier的核心区别CountDownLatch是一次性门闩CyclicBarrier是循环栅栏。前者计数器只减不增后者可重用。二、 流程图CountDownLatch核心工作流程否是 创建CountDownLatch计数器 N 线程A调用 await计数器0, 进入阻塞 线程1完成任务调用 countDown 计数器减1当前值 N-1计数器 0?⏳ 等待线程继续阻塞 门闩打开 唤醒所有等待中的线程 所有等待线程继续执行 线程2完成任务调用 countDown三、 CountDownLatch核心方法详解3.1 构造方法// 初始化计数器为3表示需要等待3个操作完成CountDownLatchlatchnewCountDownLatch(3);参数说明count表示需要等待的操作数量。若count小于0构造方法会抛出IllegalArgumentException。3.2 await() —— 等待计数器归零作用使当前线程阻塞直到计数器归零。如果计数器已经为零则立即返回不会阻塞。// 主线程等待try{latch.await();// 阻塞直到计数器归零}catch(InterruptedExceptione){Thread.currentThread().interrupt();}带超时的版本// 最多等待5秒超时则继续执行if(latch.await(5,TimeUnit.SECONDS)){System.out.println(所有任务已完成);}else{System.out.println(等待超时部分任务未完成);}3.3 countDown() —— 计数器减1作用将计数器减1。当计数器减到0时释放所有等待线程。// 每个子线程完成任务后调用latch.countDown();⚠️重要特性调用countDown()的线程不阻塞会立即继续执行。一个线程可以在不同阶段多次调用countDown()。3.4 其他常用方法方法说明getCount()返回当前计数器的值await(long timeout, TimeUnit unit)带超时的等待countDown()减少计数器四、 流程图CountDownLatch的两种经典用法 用法2: 主线程等待工作线程完成否是主线程: 创建 latchN启动N个工作线程每个线程完成任务后调用 countDown所有N个任务都完成?主线程: await返回继续执行 用法1: 工作线程等待启动信号主线程: 创建 latch1工作线程调用 await全部阻塞主线程完成准备工作主线程: latch.countDown所有工作线程同时启动4.1 用法一启动信号一次触发多线程同时启动根据Oracle官方文档中的示例CountDownLatch初始化为1时可以作为一个**“启动门闩”**所有工作线程调用await()等待直到主线程调用countDown()打开门闩。// 场景所有运动员等待发令枪响publicclassStartSignalDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{intN5;CountDownLatchstartSignalnewCountDownLatch(1);// 发令枪// 创建N个运动员工作线程for(inti0;iN;i){newThread(()-{System.out.println(Thread.currentThread().getName() 已就位等待发令...);try{startSignal.await();// 等待发令枪响System.out.println(Thread.currentThread().getName() 起跑);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}},运动员-(i1)).start();}Thread.sleep(3000);// 模拟准备时间System.out.println( 预备... 砰);startSignal.countDown();// 发令枪响所有线程同时启动}}4.2 用法二完成信号等待所有任务完成根据Oracle官方文档的示例CountDownLatch初始化为N可以用于等待N个任务全部完成。// 场景主线程等待所有玩家加载完成publicclassCompleteSignalDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{intplayerCount10;CountDownLatchdoneSignalnewCountDownLatch(playerCount);// 需要等待10个玩家// 创建10个玩家线程for(inti0;iplayerCount;i){newThread(()-{// 模拟加载过程intprogress0;while(progress100){progressnewRandom().nextInt(20);if(progress100)progress100;System.out.println(Thread.currentThread().getName() 加载: progress%);try{Thread.sleep(100);}catch(InterruptedExceptione){}}System.out.println(✅ Thread.currentThread().getName() 加载完成);doneSignal.countDown();// 计数器减1},玩家-(i1)).start();}// 主线程等待所有玩家加载完成doneSignal.await();System.out.println( 所有玩家已加载完成游戏开始);}}4.3 CountDownLatch vs Thread.join()根据源码分析CountDownLatch相比Thread.join()有两个重要优势对比维度Thread.join()CountDownLatch等待条件等待线程终止等待计数器归零灵活性必须持有线程引用countDown()可在任何地方调用线程池支持无法用于线程池✅ 完美配合线程池使用// 配合线程池使用ExecutorServiceexecutorExecutors.newFixedThreadPool(3);CountDownLatchlatchnewCountDownLatch(3);for(inti0;i3;i){executor.submit(()-{// 执行任务latch.countDown();// 无需持有线程引用});}latch.await();// 主线程阻塞executor.shutdown();五、 底层原理AQS共享模式5.1 核心架构CountDownLatch内部通过一个继承自AbstractQueuedSynchronizerAQS的内部类Sync来实现同步控制。所有对计数器的操作都转交给Sync实例处理。CountDownLatch内部类 Sync继承 AQSstate 字段存储计数器值tryAcquireShared检查 state0tryReleaseSharedCAS减15.2 await() 的底层流程当调用await()时转交给Sync处理检查state计数器是否等于0如果state 0直接返回门已开如果state 0当前线程进入AQS同步队列阻塞等待5.3 countDown() 的底层流程当调用countDown()时尝试通过CAS将state减1如果减1后state 0调用releaseShared()释放所有等待线程AQS共享模式下releaseShared()会级联唤醒同步队列中的所有阻塞线程内存可见性保证根据官方文档在计数归零之前一个线程中调用countDown()之前的操作happen-before另一个线程从await()成功返回之后的操作。六、 避坑指南6.1 常见陷阱序号陷阱正确做法①计数器与任务数不匹配确保countDown()调用次数等于初始计数器②未处理InterruptedException捕获后恢复中断状态或向上抛出③误以为可以重置计数器CountDownLatch不可重置如需重用用CyclicBarrier④在finally中忘记countDown()即使在异常情况下也要确保计数器减少6.2 最佳实践清单序号实践建议说明①将countDown()放在finally中确保异常时计数器也能归零②使用await(timeout)防止永久阻塞避免线程无限等待③初始计数器与任务数量保持一致否则死锁或过早触发④需要重置时使用CyclicBarrierCountDownLatch是一次性工具The End点点关注收藏不迷路⬆ ⬆ 顶部 ⬆ ⬆