在 Java 应用中,线程的 Waiting on condition 状态是 jstack 线程转储里最常见的 Waiting 状态之一。它表示线程正在等待某个条件变为真,典型的触发方式包括调用 Object.wait()、LockSupport.park() 以及 Condition.await()。当服务响应变慢甚至完全停滞时,我们通常会抓取几次 jstack 快照,如果发现一组线程反复处于 Waiting on condition 状态且迟迟不能退出,就要警惕逻辑死锁的可能。这类死锁不会触发 jstack 底部的“Found one Java-level deadlock”提示,因为它并非由内置锁的互斥竞争引起,而是由线程间复杂的条件等待关系所导致。

Waiting on condition 的底层含义与常见场景
要理解逻辑死锁,必须先弄清 Waiting on condition 到底是什么。在 HotSpot JVM 中,线程状态通过 java.lang.Thread.State 枚举定义,其中 WAITING 和 TIMED_WAITING 两种状态在 jstack 输出中都会表现为 waiting on condition。区别仅在于是否设置了超时时间。例如,一个线程调用 Object.wait() 且没有超时参数时,就处于无期限的 WAITING 状态;如果调用 Thread.sleep(long millis) 或 LockSupport.parkNanos(),则处于 TIMED_WAITING 状态,但其根因同样是等待某个条件。
从操作系统线程调度层面看,Waiting on condition 对应的是线程被移出 CPU 的运行队列,放入条件变量关联的等待队列。直到其他线程调用了 notify()、notifyAll() 或 LockSupport.unpark(),等待的线程才会被重新唤醒并竞争 CPU。对于基于 AQS 的显式锁(如 ReentrantLock)中的 Condition.await(),原理类似:线程释放锁,将自己挂起在 Condition 的等待队列上,等待 signal 或 signalAll 的触发。如果所有的 signal 都迟迟不来,线程就会一直停留在 waiting on condition。
常见的 Waiting on condition 出现场景包括:线程间通过 wait/notify 协作的生产者-消费者模型;显式锁的 Condition 用于实现阻塞队列或有界缓冲区;以及使用 Future.get() 或线程池 awaitTermination 时,底层都会调用 LockSupport.park()。这些场景本身是正常的,但一旦依赖的唤醒条件因为代码 bug 没有被满足,就会演变成永久等待。
逻辑死锁与内置锁死锁的本质区别
Java 开发者最为熟悉的死锁是内部锁(synchronized)死锁,即两个或多个线程互相持有对方需要的锁,形成循环等待。jstack 能够自动识别这种死锁,并在线程转储末尾打印出清晰的死锁链条。逻辑死锁则不在 jstack 的自动检测范围内,因为它不涉及内置锁的互斥竞争,而是源于线程在条件等待上的循环依赖。
一个典型的逻辑死锁场景是:线程 A 持有锁 L1,并在 Condition C1 上等待,而 C1 的唤醒必须由持有 L1 的线程来触发;同时线程 B 又持有锁 L1,并在另一个 Condition C2 上等待,C2 又需要线程 A 来唤醒。由于双方都陷入了等待,谁也无法去执行唤醒操作,这就构成了逻辑上的死锁。更隐蔽的情况是,死锁发生在不同对象之间,例如线程 A 等待某个缓存条目被线程 B 填充,而线程 B 又在等待线程 A 释放的某个数据库连接。这些依赖关系通过对象而非锁来传递,jstack 不可能自动分析。
与内置锁死锁相比,逻辑死锁的诊断更依赖对线程栈帧和锁对象的“人工关联”。查看 jstack 转储时,不仅需要看线程状态,还要仔细阅读栈信息中的 - waiting to lock <...> 和 - locked <...> 标记,以及显式锁的 AbstractQueuedSynchronizer$ConditionObject 等待信息。同时,需要对比多个线程的调用上下文,找到那个“你等我、我等你”的闭环。
用 jstack 快速诊断逻辑死锁的实战步骤
假设生产环境的订单服务突然停止处理请求,线程池中所有工作线程都在 Waiting on condition。我们连续抓取三次 jstack(间隔2-3秒),发现以下典型堆栈:
"pool-1-thread-5" #25 prio=5 os_prio=0 tid=0x00007f8c9408f800 nid=0x1b6f waiting on condition [0x00007f8c5b2e7000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x000000076b0c8a70> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at com.example.OrderService.lockAndWait(OrderService.java:42) ...
堆栈显示线程在 Condition.await() 处等待,等待的对象是 0x000000076b0c8a70。同一个 Condition 对象也可能被其他线程等待。我们需要查找其他线程是否也在等待同一个 Condition 对象,或者有没有线程能够发出 signal。继续分析另一个线程:
"pool-1-thread-3" #23 prio=5 os_prio=0 tid=0x00007f8c9408b000 nid=0x1b6d waiting on condition [0x00007f8c5b5e9000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x000000076b0c8a70> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at com.example.OrderService.lockAndWait(OrderService.java:42) ...
两者都停在同一个 Condition 地址 0x000000076b0c8a70 上,但查看锁持有情况会发现,当前没有线程持有对应的 ReentrantLock(或者持有锁的线程也在等待其他条件)。进一步检查代码 OrderService.lockAndWait,逻辑可能是:线程先获取锁,判断某个资源未就绪后进入 condition.await(),而资源就绪依赖另一个线程在同样的锁保护下更新标志并调用 condition.signalAll()。如果负责更新的线程因为异常或其他原因没有走到 signal 逻辑,或者它自身也卡在其他等待点上,就会导致大量工作线程堆积在 waiting on condition。
定位这类问题的关键是:列出所有处于 WAITING 或 TIMED_WAITING 的线程,按它们等待的条件对象(如 ConditionObject 地址、等待的锁地址)进行分组。如果发现某个条件对象上有大量线程等待,并且找不到能够释放该条件的活动线程,就极可能发生了逻辑死锁。接着反向查看持有相关锁的线程的完整堆栈,确认它是否也阻塞在某个等待上,从而形成等待闭环。
例如,可以结合操作系统的线程 dump 分析工具,或者用 jstack -l 输出额外锁信息,快速定位哪些线程拥有 java.util.concurrent.locks.ReentrantLock$NonfairSync 等显式锁。如果持有锁的线程本身也处于 waiting on condition 等待另一个资源,就验证了循环依赖。最终修复方案往往是打破循环,比如增加超时等待、将信号发起逻辑提取到独立线程、或者用更高级的同步器(如 CountDownLatch、CyclicBarrier)替代容易出错的 Condition 手动管理。
还有一种常见逻辑死锁由数据库连接池耗尽引起。每个线程都在 waiting on condition 等待从连接池中获取连接,而持有连接的线程又在等待其他线程返回的连接。通过 jstack 可以快速识别出所有线程卡在 DataSource.getConnection() 调用上,进而切换到连接池配置和事务超时治理。无论哪种形式,熟练掌握 jstack 中 Waiting on condition 的解读方法,都能让你在关键时刻做到心中有数,快速恢复生产环境。
jstackWaiting_on_condition逻辑死锁修改时间:2026-08-12 12:40:16