不一定。大量线程处于 WAITING 状态本身不是故障信号,而是线程协作的正常表现;关键要看「谁在等、等什么、等了多久、是否该被唤醒」。

不一定。大量线程处于 WAITING 状态本身不是故障信号,而是线程协作的正常表现;关键要看「谁在等、等什么、等了多久、是否该被唤醒」。
WAITING 状态的常见合法来源
Java 中进入 WAITING 的方式有限且明确,绝大多数是主动协作行为:
-
Object.wait():线程持有对象锁后调用,等待notify()或notifyAll()—— 常见于自定义生产者-消费者、条件等待逻辑 -
Thread.join()(无参):主线程等子线程结束 —— 多见于批处理、测试代码、初始化同步块 -
LockSupport.park():JUC 工具类(如AbstractQueuedSynchronizer)底层使用 ——ReentrantLock.lock()、CountDownLatch.await()等最终都会落到这个调用上
这些都不是“卡死”,而是线程在语义上明确选择「暂停执行,直到被通知」。比如一个 CountDownLatch.await() 调用后状态就是 WAITING,只要倒计时没归零,它就该待着。
什么时候 WAITING 才值得警惕
真正的问题不在于状态本身,而在于「等待是否合理、是否可退出、是否有唤醒路径」:
- 线程 dump 显示大量线程卡在
Object.wait(),但对应对象上从未有notify()被调用 —— 可能是条件判断写错、唤醒逻辑遗漏或异常绕过了唤醒分支 - 所有
WAITING线程都等同一个对象(比如某个静态Object实例),且该对象生命周期过长、无复位机制 —— 容易积累成“僵尸等待”,尤其在热部署或模块重载场景 - 线程池中本该复用的 worker 线程长期
WAITING在take()之外的地方(例如自定义阻塞队列的poll()实现里误用了wait())—— 说明线程池未按预期工作,可能任务无法分发或消费端阻塞 - 结合监控发现:线程数持续增长 +
WAITING线程数同步上涨 + 无对应业务请求量变化 —— 很可能是线程泄漏,比如每次请求都 new 一个 Thread 并 join,但 join 后没释放引用
如何快速验证 WAITING 是否健康
别只看状态数量,要抓上下文:
- 用
jstack <pid></pid>抓快照,搜索java.lang.Thread.State: WAITING,重点看括号里的提示:(on object monitor)表示等wait(),(parking to wait for )表示park(),二者唤醒机制完全不同 - 对
WAITING (on object monitor)线程,往上翻几行栈,找到at xxx.wait(NativeMethod)上方的synchronized块 —— 看锁对象是谁、谁可能负责 notify - 对
WAITING在LockSupport.park的线程,看它上层是不是AbstractQueuedSynchronizer相关调用 —— 这通常是 JUC 正常行为,除非你看到大量线程停在同一个 AQS 队列头却无进展 - 对比
jstat -gc <pid></pid>和线程数趋势:如果 GC 频率低、堆内存稳定,但WAITING线程越来越多,大概率不是资源不足导致的被动等待,而是逻辑卡点
最常被忽略的一点:WAITING 线程不消耗 CPU,但会占 JVM 线程资源和栈内存。当数量达到几千甚至上万时,即使每个只占 1MB 栈空间,也会迅速耗尽虚拟内存或触发 OOM;这时候问题不是“功能是否正常”,而是“能不能再扛住一次流量高峰”。











