CountDownLatch 实现精准的并发控制
CountDownLatch 实现精准的并发控制概述本文档详细分析并发启动场景赛跑模式中两个 CountDownLatch 的作用和阻塞关系。代码示例importjava.util.concurrent.CountDownLatch;publicclassRaceStartDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{finalintrunnerCount10;finalCountDownLatchreadyLatchnewCountDownLatch(runnerCount);// 计数器10finalCountDownLatchstartLatchnewCountDownLatch(1);// 计数器1// 启动10个运动员线程for(inti0;irunnerCount;i){finalintidi;newThread(()-{try{System.out.println(运动员 id 准备就绪);readyLatch.countDown();// ← 不阻塞只是计数-1startLatch.await();// ← 阻塞的是运动员线程System.out.println(运动员 id 起跑);}catch(InterruptedExceptione){e.printStackTrace();}}).start();}// 主线程裁判readyLatch.await();// ← 阻塞的是主线程System.out.println(\n所有运动员就位准备发令);Thread.sleep(500);startLatch.countDown();// ← 主线程执行唤醒所有运动员System.out.println(砰);}}两个 Latch 对比Latch谁调用 await()阻塞的是谁谁调用 countDown()初始计数作用readyLatch主线程主线程各个 Runner 线程10主线程等待所有运动员就位startLatchRunner 线程Runner 线程主线程1运动员等待发令枪执行流程图┌─────────────────────────────────────────────────────────────┐ │ Phase 1: 准备阶段 │ ├─────────────────────────────────────────────────────────────┤ │ Runner-1: readyLatch.countDown() (不阻塞) readyLatch: 10→9 │ │ Runner-2: readyLatch.countDown() (不阻塞) readyLatch: 9→8 │ │ ... │ │ Runner-10: readyLatch.countDown() (不阻塞) readyLatch: 1→0 │ │ │ │ 主线程: readyLatch.await() ←─────── 阻塞主线程 │ │ 等待 readyLatch 从 10 减到 0 │ └─────────────────────────────────────────────────────────────┘ ↓ readyLatch 0 主线程被唤醒 ┌─────────────────────────────────────────────────────────────┐ │ Phase 2: 起跑阶段 │ ├─────────────────────────────────────────────────────────────┤ │ Runner-1: startLatch.await() ←─────── 阻塞运动员线程 │ │ Runner-2: startLatch.await() ←─────── 阻塞运动员线程 │ │ ... │ │ Runner-10: startLatch.await() ←────── 阻塞运动员线程 │ │ │ │ 主线程: startLatch.countDown() startLatch: 1→0 │ │ (发令枪) │ └─────────────────────────────────────────────────────────────┘ ↓ startLatch 0 所有运动员线程被唤醒开始跑形象类比赛跑场场景 赛跑场场景 ♂️ Runner-1 ♂️ Runner-2 ♂️ Runner-10 │ │ │ │准备就绪 │准备就绪 │准备就绪 ▼ ▼ ▼ countDown() countDown() countDown() (报到) (报到) (报到) 裁判(主线程) │ readyLatch.await() │ ⏳ 等待中... (所有人都报到了readyLatch 0) │ startLatch.countDown() ←─── 砰发令枪 ────→ │ 所有 Runner 被唤醒开始跑详细时间线时间轴 → T0: 主线程启动 10 个 Runner 线程 T1: Runner-1 执行 readyLatch.countDown() (readyLatch: 10→9) T2: Runner-2 执行 readyLatch.countDown() (readyLatch: 9→8) ... T10: Runner-10 执行 readyLatch.countDown() (readyLatch: 1→0) T11: 主线程的 readyLatch.await() 返回主线程继续执行 T12: 主线程打印 所有运动员就位准备发令 T13: 主线程 sleep 500ms T14: 主线程执行 startLatch.countDown() (startLatch: 1→0) T15: 所有阻塞在 startLatch.await() 的 Runner 线程被唤醒 T16: 所有 Runner 开始跑关键点总结❌ 常见误解错误理解两个 Latch 阻塞的都是主线程✅ 正确理解Latch阻塞的线程谁来唤醒readyLatch主线程Runner 线程们通过 countDownstartLatchRunner 线程们主线程通过 countDown为什么需要两个 Latch只用一个 CountDownLatch 无法实现这种精确的同步只用 readyLatch主线程可以等待运动员就位但无法控制运动员同时起跑只用 startLatch运动员可以等待发令枪但主线程不知道运动员是否都准备好了双 Latch 设计实现了确保所有运动员都就位后裁判才发令确保所有运动员同时收到发令信号公平起跑适用场景这种双 Latch 模式适用于✅ 需要多个线程同时启动的场景✅ 需要主线程确认所有子线程准备就绪✅ 需要精确控制并发开始时机✅ 批量任务并行执行要求同时开始与 CyclicBarrier 的对比特性CountDownLatch双 LatchCyclicBarrier等待方向互为依赖互相等待所有线程互相等待重用性不可重用可循环使用线程数量主线程 子线程所有线程地位平等典型场景主控并发并行计算分阶段总结核心要点readyLatch 阻塞主线程主线程等待所有子线程准备就位startLatch 阻塞子线程子线程等待主线程发令互相等待实现精确的并发控制一次性使用CountDownLatch 计数归零后无法重置阻塞关系速记┌─────────────┐ ┌─────────────┐ │ 主线程 │────────▶│ readyLatch │ 阻塞主线程 │ (裁判) │ └─────────────┘ └─────────────┘ ▲ │ │ Runner 线程们 │ countDown() │ ┌─────────────┐ ┌─────────────┐ │ Runner们 │◀────────│ startLatch │ 阻塞 Runner 们 │ (运动员) │ └─────────────┘ └─────────────┘ ▲ │ │ 主线程 │ countDown()发令枪总结readyLatch 让裁判等待运动员startLatch 让运动员等待裁判。