导读:本期聚焦于小伙伴创作的《如何通过 jstack 线程转储文件中的 Waiting on condition 状态快速定位生产环境的逻辑死锁》,敬请观看详情。生产环境中应用突然停止响应,线程堆栈里大量处于 Waiting on condition 的线程会让你瞬间紧张。这个状态通常与 Object.wait() 或 LockSupport.park() 调用相关,当多个线程相互等待对方释放的条件时,就演变成“逻辑死锁”。与传统的 synchronized 死锁不同,jstack 不会直接给出死锁提示,靠的是对线程调用栈和锁持有关系的理性分析。本文将剖析 Waiting on condition 的底层含义,详细对比逻辑死锁与内置锁死锁的本质区别,并通过一个真实的诊断案例展示如何从转储文件中快速提取相互等待的线程组,手把手带你定位根因。

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

如何通过 jstack 线程转储文件中的 Waiting on condition 状态快速定位生产环境的逻辑死锁

Waiting on condition 的底层含义与常见场景

要理解逻辑死锁,必须先弄清 Waiting on condition 到底是什么。在 HotSpot JVM 中,线程状态通过 java.lang.Thread.State 枚举定义,其中 WAITINGTIMED_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 等待另一个资源,就验证了循环依赖。最终修复方案往往是打破循环,比如增加超时等待、将信号发起逻辑提取到独立线程、或者用更高级的同步器(如 CountDownLatchCyclicBarrier)替代容易出错的 Condition 手动管理。

还有一种常见逻辑死锁由数据库连接池耗尽引起。每个线程都在 waiting on condition 等待从连接池中获取连接,而持有连接的线程又在等待其他线程返回的连接。通过 jstack 可以快速识别出所有线程卡在 DataSource.getConnection() 调用上,进而切换到连接池配置和事务超时治理。无论哪种形式,熟练掌握 jstack 中 Waiting on condition 的解读方法,都能让你在关键时刻做到心中有数,快速恢复生产环境。

jstackWaiting_on_condition逻辑死锁修改时间:2026-08-12 12:40:16

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。