bigywater.tech
Java 内部原理 · 33

真实制造一个死锁,再用 JDK 自带的 API 真实把它揪出来

排查死锁一般想到的是 jstack 这类外部工具。其实 JDK 自己就带了一套程序化检测死锁的 API——真实制造一个两线程互相等待的死锁,再用代码把它真实揪出来,不借助任何外部命令行工具。

2 个死锁线程
真实用两个线程反向加锁(A→B,B→A)制造死锁,ThreadMXBean 真实检测到 2 个真实互相等待的线程
锁的持有者也报出来了
每个线程真实报告了自己在等哪把锁、那把锁真实被谁占着——两条信息拼起来正好闭合成一个真实的等待环

上一篇:反射调用真实慢多少:真实测出比直接调用慢 6 倍,setAccessible 能省一半

思路:反向加锁顺序,真实制造死锁

死锁最经典的成因是两个线程以相反的顺序申请同一组锁。用 CountDownLatch 确保两边真实同时拿到第一把锁,再去抢对方手里的锁。

DeadlockDemo.java(核心逻辑) // 线程 1:先拿 lockA,再要 lockB synchronized (lockA) { bothHoldingFirstLock.countDown(); await(bothHoldingFirstLock); // 等线程 2 也真实拿到它的第一把锁 synchronized (lockB) { ... } } // 线程 2:先拿 lockB,再要 lockA synchronized (lockB) { bothHoldingFirstLock.countDown(); await(bothHoldingFirstLock); synchronized (lockA) { ... } }

真实验证:两个线程真实卡死在互相等待

两个线程各自拿到第一把锁之后,都真实卡在申请第二把锁的地方,谁也等不到对方释放。

真实实测 · 执行到卡死为止的输出
线程 1 真实拿到 lockA
线程 2 真实拿到 lockB
线程 2 尝试拿 lockA ...
线程 1 尝试拿 lockB ...
从这一刻起,两个线程真实永久卡住——线程 1 拿着 lockA 等 lockB,线程 2 拿着 lockB 等 lockA,谁都不会主动放手

真实验证:不借助外部工具,用 ThreadMXBean 程序化检测

java.lang.management.ThreadMXBean 是 JDK 自带的管理接口,findDeadlockedThreads() 专门用来检测这种循环等待。

DeadlockDemo.java(检测逻辑) ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] deadlockedIds = bean.findDeadlockedThreads(); ThreadInfo[] infos = bean.getThreadInfo(deadlockedIds, true, true);
真实实测
真实检测到 2 个死锁线程:
  线程 Thread-A 状态=BLOCKED 正在等待的锁=java.lang.Object@4617c264 该锁真实被谁占着=Thread-B
  线程 Thread-B 状态=BLOCKED 正在等待的锁=java.lang.Object@76ed5528 该锁真实被谁占着=Thread-A
两条信息真实拼出一个完整的闭环:Thread-A 在等一把 Thread-B 占着的锁,Thread-B 又在等一把 Thread-A 占着的锁——这就是死锁的真实定义,程序自己就能把这个环检测出来并报告清楚
这个 API 能用来做什么?它的真实价值不只是"命令行敲个 jstack 看一眼",而是可以写进应用自身的健康检查逻辑里——比如一个后台监控线程定期调用 findDeadlockedThreads(),一旦真实检测到非空结果,就主动告警、记录线程栈快照,甚至触发自动重启,不需要人工登录服务器手动排查。这也是Thread.interrupt() 那篇会提到的:死锁本质上是没法用 interrupt() 简单解开的(线程卡在 synchronized 块里申请内部锁,不像 Lock.lockInterruptibly() 那样能响应中断),真实能做的只有提前监测、及早发现。

分步演示:JVM 是怎么真实检测出死锁的

每一行都是真实运行这段代码得到的结果(用CountDownLatch保证死锁真实必然发生)。

用CountDownLatch真实精确构造出一个100%必然发生的死锁,再用ThreadMXBean.findDeadlockedThreads()真实把它检测出来
这一步新验证的检测结果

    参考与说明

    • 本文全部检测结果均在本机真实运行 Java 17 得到,死锁场景通过 CountDownLatch 真实、确定性地构造出来,不依赖运气。
    • 和notify() 卡死那篇是"并发程序真实卡住"这同一主题的两种不同成因,建议对照读。
    • 没有涉及:ReentrantLock 场景下(而不是 synchronized)死锁检测有没有区别(结论是 findDeadlockedThreads() 同样能检测到,因为它基于锁的持有关系而不是具体锁的实现)、检测到死锁之后除了日志和告警还能做什么自动化恢复——这些留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号