Java中CountDownLatch为何未按预期计数停止并触发后续逻辑?
为什么CountDownLatch的"Completed."没有在第6次"Started."后输出?
先把你提供的代码格式化一下,方便查看:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Processor implements Runnable { private CountDownLatch latch; public Processor(CountDownLatch latch) { this.latch = latch; } public void run() { System.out.println("Started."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } latch.countDown(); } } public class App { public static void main(String[] args) { CountDownLatch latch = new CountDownLatch(5); // 从5倒数到0 ExecutorService executor = Executors.newFixedThreadPool(2); // 线程池有2个线程 for(int i=0; i < 10; i++) { executor.submit(new Processor(latch)); // 每次提交任务,任务完成时会调用countDown } try { latch.await(); // 等待latch倒数到0 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Completed."); } }
你提到预期是当计数从5减到0时,"Completed."应该在第6次"Started."后输出,但实际总是在第7或8次之后出现。这其实是因为你混淆了任务启动(打印"Started.")和countDown()执行的时机,我来给你拆解一下整个流程:
核心误解点
你以为countDown()是在任务打印"Started."的时候执行,但实际上,每个任务的执行顺序是:
- 打印"Started." → 任务启动
- 休眠3秒 → 模拟耗时操作
- 才调用countDown() → 让latch的计数减1
而你的线程池只有2个线程,任务是排队执行的,我们一步步理清楚时间线:
第0-3秒:
- 线程池的2个线程同时启动任务1和任务2,分别打印
Started.(第1、2次),然后进入3秒休眠。 - 剩下的8个任务在队列里等待执行。
第3秒左右:
- 任务1的休眠结束,调用
countDown()→ latch从5变成4。 - 线程1立刻去执行队列里的任务3,打印
Started.(第3次),然后休眠3秒。 - 任务2的休眠结束,调用
countDown()→ latch从4变成3。 - 线程2立刻去执行队列里的任务4,打印
Started.(第4次),然后休眠3秒。
第6秒左右:
- 任务3的休眠结束,调用
countDown()→ latch从3变成2。 - 线程1执行任务5,打印
Started.(第5次),休眠3秒。 - 任务4的休眠结束,调用
countDown()→ latch从2变成1。 - 线程2执行任务6,打印
Started.(第6次),休眠3秒。
第9秒左右:
- 任务5的休眠结束,调用
countDown()→ latch从1变成0!此时主线程的latch.await()被唤醒,开始打印Completed.。 - 线程1立刻去执行队列里的任务7,打印
Started.(第7次),然后休眠3秒。 - 任务6的休眠结束,调用
countDown()→ 此时latch已经是0了,这个操作不会有任何影响。 - 线程2执行任务8,打印
Started.(第8次),然后休眠3秒。
所以你看,当latch计数到0的时候,线程1已经开始执行任务7了,Completed.会和第7次(甚至第8次)的Started.交替出现,这完全取决于JVM的线程调度顺序,这就是为什么你看到的结果总是在第7或8次之后出现。
验证你的预期
如果你想让Completed.在第5次countDown()后立刻出现,也就是在第6次Started.之后,你可以把countDown()移到Thread.sleep()之前,也就是任务启动后立刻减计数:
public void run() { System.out.println("Started."); latch.countDown(); // 移到休眠前 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }
这时候,前5个任务启动后就会把latch减到0,主线程会在第5次Started.之后(也就是第6次Started.之前或同时)打印Completed.,符合你最初的预期。
内容的提问来源于stack exchange,提问作者iramshiv
相关产品推荐
相关产品推荐

