BLOCKED 状态不等于死锁,需结合 jstack 中 waiting to lock 与 locked 的锁地址是否闭环来判断;加 -l 参数可识别 ReentrantLock 等显式锁;WAITING/TIMED_WAITING 也可能构成广义死锁。

怎么看 java.lang.Thread.State: BLOCKED 是不是真死锁
看到 BLOCKED 状态别急着画圈标“死锁”,它只是线程在等锁,不等于卡死了。真正要盯的是「它在等谁的锁」和「谁拿着这把锁不放」。jstack 输出里每条线程下面会跟一行 - waiting to lock <code>0x...,这个地址就是目标锁对象;再找有没有别的线程显示 - locked <code>0x... 对应同一个地址——这才是闭环证据。
常见误判点:
- 多个线程都
BLOCKED在同一把锁上,但只有一个线程locked它 → 正常排队,不是死锁 - 线程 A
waiting to lock 0x123,线程 Bwaiting to lock 0x456,而 A 又locked 0x456、B 又locked 0x123→ 死锁成立 -
BLOCKED线程堆栈停留在synchronized方法入口或代码块开头 → 锁竞争热点,未必是死锁,但可能是性能瓶颈
WAITING 和 TIMED_WAITING 状态为什么也要查
死锁不一定只发生在 BLOCKED。比如线程 A 调用 Object.wait() 进入 WAITING,同时持有某把锁;线程 B 拿着另一把锁,又想调用 A.notify() ——但 A 持有的锁正好是 B 等的那把,B 卡在 BLOCKED,A 卡在 WAITING,互相等,也算广义死锁(活锁+资源依赖闭环)。
关键看三件事:
- 该线程是否
locked了某个对象(jstack 里有明确- locked <code>0x...行) - 它
waiting on的 monitor 地址,有没有被别的线程locked且迟迟不notify - 堆栈里是否出现
LockSupport.park、Condition.await、Object.wait这类主动让出 CPU 的调用 —— 它们会让线程停在WAITING,但背后可能藏着锁依赖
用 jstack -l 多打一个 -l 参数到底值不值
值。默认 jstack 不显示显式锁(java.util.concurrent.locks 体系),只报 synchronized 相关的锁信息。加了 -l,你会看到类似这样的行:
java.util.concurrent.locks.ReentrantLock$NonfairSync@7f8b4a3c held by thread 12
这意味着你能追踪到 ReentrantLock.lock() 引发的阻塞,否则这些线程只会显示 WAITING 或 parking,完全看不出锁归属。线上如果用了大量 Lock 替代 synchronized,不加 -l 基本没法定位。
注意两点:
-
-l会稍微延长 jstack 执行时间(需遍历所有 Lock 实例),但对运行中 JVM 几乎无影响 - 某些老 JDK(如 6u21 之前)不支持
-l,会报错;建议至少用 JDK 7u6+ 或 JDK 8+
怎么快速从几百行 jstack 输出里揪出可疑线程
别人工扫。用命令管道筛:
grep -A 10 -B 2 "BLOCKED\|WAITING.*on.*0x\|locked.*0x" threaddump.log | grep -E "(java\.lang\.Thread\.State|locked|waiting to lock|waiting on)"
更实用的是先提取所有锁地址,再反查谁持有着谁等着:
- 提取所有
locked 0x[0-9a-f]+和waiting to lock 0x[0-9a-f]+地址,去重 - 对每个地址,搜索整个 dump:出现几次
locked?几次waiting to lock?如果两者都 ≥1,且跨线程 → 高风险 - 特别留意
Finalizer、Reference Handler这类系统线程也进入WAITING—— 往往说明 GC 压力大或 finalize 方法阻塞,间接拖垮业务线程
最麻烦的情况是锁地址每次 dump 都变(比如短生命周期对象作锁),这时得结合业务逻辑看 synchronized 块锁的是什么变量,而不是只盯地址。








