Waiting on condition 表示线程在 Object.wait()、LockSupport.park() 或 AQS 条件队列上挂起,可能是正常空闲等待,也可能是因无 signal 调用导致的逻辑卡死;它非死锁,jstack 不检测此类 condition 死锁。

什么是 Waiting on condition 的真实含义
Waiting on condition 不代表死锁,而是线程在 Object.wait()、LockSupport.park() 或某些 JDK 内部条件队列上挂起——它可能是正常阻塞(如线程池空闲等待任务),也可能是逻辑卡死。生产环境里误判这个状态为“死锁”是常见误区,真正要抓的是「本该被唤醒却长期没被唤醒」的线程链。
- 这个状态不会出现在
jstack -l输出的java.lang.Thread.State: BLOCKED中,后者才是传统 Monitor 锁竞争 - 它常伴随
parking to wait for、waiting on或具体条件队列地址(如AbstractQueuedSynchronizer$ConditionObject@...) - 如果多个线程都
Waiting on condition且共享同一个Condition实例,但没有线程处于SIGNAL或runnable状态去调用signal()/signalAll(),才值得怀疑
如何从 jstack 文件中筛出可疑的 condition 等待链
别通读全文。用命令快速聚焦:
grep -A 5 -B 5 "Waiting on condition" threaddump.log | grep -E "(java\.lang\.Thread|at |waiting on |parking to wait for)"
重点关注三类模式:
- 多个线程共用同一
ConditionObject@0x...地址,且无任何线程在执行signal相关方法(检查调用栈里有没有AbstractQueuedSynchronizer$ConditionObject.signal或自定义signal调用) - 线程在
await()后长期停留,而其所属的ReentrantLock持有者已消失(比如持有锁的线程是TERMINATED或根本不在 dump 中) - 等待栈顶是
LinkedBlockingQueue.take()、ArrayBlockingQueue.put()等,但生产者/消费者端线程全部卡在同类状态,形成闭环
为什么 jstack 无法直接报告 condition 死锁
JDK 自带的死锁检测(jstack -l 结尾的 Found 1 deadlock.)只识别 Monitor 锁(synchronized 和 ReentrantLock.lock())的循环等待,完全不覆盖 Condition.await()。
-
Condition是基于 AQS 的独立等待队列,唤醒依赖显式 signal,jstack 不分析 signal 路径是否可达 - 即使你看到 5 个线程都在
await(),只要没人调用signal,jstack 就认为“一切正常” - 真实逻辑死锁往往藏在业务代码:比如 A 线程 await 前需 B 线程先完成某操作,而 B 线程又 await 等 A 发信号 —— jstack 只显示“都在等”,不显示“等谁”和“谁该发”
定位时必须交叉验证的三个现场信息
单看 jstack 很容易掉坑:
- 查对应线程的完整栈,确认
await()调用是否包裹在lock.lock()/unlock()正确配对中;漏掉unlock()会导致锁未释放,间接让 signal 线程拿不到锁而无法执行 signal - 对比应用日志时间戳:如果某线程从 2 小时前就显示
Waiting on condition,且期间没有任何相关业务日志输出(如“开始处理”“触发 signal”),基本可判定唤醒路径中断 - 检查 JVM 参数是否禁用了偏向锁或设置了
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC等影响 AQS 内部行为的选项——极少数情况下 GC 暂停过长会让 park 线程错过 signal,但这需要 gc.log 配合判断
实际排查中最容易被忽略的,是 condition 所属 lock 的公平性设置:new ReentrantLock(true) 下,await 唤醒顺序受排队策略影响,若 signal 频率低,后入队线程可能永远轮不到。


















