主线程调用 await() 后卡住,本质是 countDown() 被跳过;需检查所有执行路径(if/else、try-catch、提前return等)是否全覆盖,优先在 finally 中调用并验证其执行,确认任务线程真正运行、回调注册成功且触发,严格保证初始化值 N 对应且仅 N 次 countDown(),务必使用带超时的 await 并记录剩余计数以快速定位漏点。

主线程调用 await() 后卡住不动,不是线程“卡死”,而是它在等一个永远不会来的信号——countDown() 被跳过了。
查所有执行路径是否都覆盖了 countDown()
别只盯着“成功分支”看。常见漏点藏在:
- if/else 或 switch 中,只在某个 case 里写了
countDown(),但实际走的是 default 或其他分支 - try-catch 块里,业务逻辑抛异常,catch 块只打日志或静默返回,没调
countDown() - 方法提前
return、循环中continue或break,绕过了后续的倒计时语句 - 用
finally是稳妥做法,但要确认它真被执行了(比如线程被强制中断时,finally 可能不执行)
确认触发 countDown 的线程确实跑起来了
写了个新线程或提交了任务,不等于它就执行了:
- 检查线程池状态:是否已
shutdown()?是否因队列满+拒绝策略为DiscardPolicy而静默丢弃任务? - 用
executor.submit()后,别只看返回值非 null 就以为 OK;加一行日志,比如在任务开头打印"[task-start] " + Thread.currentThread().getName(),验证有没有输出 - 异步回调(如
CompletableFuture.thenRun()、RxJavasubscribe())是否注册成功?上游有没有真正完成?可加whenComplete观察是否进入回调
盯紧 countDown 的次数和时机
CountDownLatch 不校验“谁调的”,只认数字。错一次,就永远等不到零:
- 初始化为 N,就必须有且仅有 N 次
countDown()—— 多一次不会报错,但可能掩盖问题;少一次,主线程就挂起 - 避免在重试逻辑里无条件
countDown():比如任务失败后重试三次,每次都在finally里减一,结果减了四次 - 每次调用前加日志:
log.debug("latch.countDown(), remaining={}", latch.getCount());如果某次日志没打出,说明执行流在那之前就断了
用带超时的 await 快速暴露问题
永远不用无参 await()。换成:
latch.await(15, TimeUnit.SECONDS)
如果返回 false,立刻记录告警,并 dump 当前剩余计数:latch.getCount()。这个数字就是你还差几次 countDown() —— 它直接告诉你漏在哪条路径上。

















