Java死锁怎么解决 Java死锁排查与修复【实战】

Java死锁是线程互相等待锁而阻塞,并非直接崩溃,需通过jstack、JConsole等工具主动检测,结合统一锁序、tryLock超时、缩小同步范围及使用并发工具来修复和预防。

Java死锁不是“遇到就崩”,而是线程互相卡住、谁也不让步——关键在提前发现、快速定位、精准修复。下面直接说实战中管用的办法。

怎么快速发现是不是死锁

别等线上报警才想起查死锁。开发和测试阶段就要主动探查:

  • jstack + 进程ID:运行 jstack ,搜 deadlock 或看线程状态是否大量为 BLOCKED 且锁等待链成环;
  • 用 JConsole 或 VisualVM:连上 JVM,点“检测死锁”按钮,它会自动标出涉及的线程和锁对象;
  • 加 JVM 参数 -XX:+PrintConcurrentLocks(配合 jstack)或 -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput(JDK9+ 更细粒度);
  • 业务关键路径里,对高争用资源加轻量级“锁持有超时日志”,比如记录某线程持锁超过500ms就打warn,常能暴露隐患。

怎么看懂 jstack 输出里的死锁线索

重点盯三块内容,不用全读:

  • 线程名和状态:比如 "Thread-1" #12 prio=5 os_prio=0 tid=0x00007f... nid=0x1a46 waiting for monitor entry —— 表示它想进 synchronized 块但进不去;
  • locked :说明它当前持有一个对象锁;
  • waiting to lock :它正等着另一个锁——如果另一个线程反过来也 waiting to lock 它持有的那个锁,就闭环了。

典型死锁片段长这样:

"Thread-A" ... locked ... waiting to lock
"Thread-B" ... locked ... waiting to lock

修复死锁的 4 种靠谱做法

不靠猜,靠约束和设计:

  • 统一锁顺序:多个锁必须按固定顺序获取。比如所有代码都先 lock A 再 lock B,绝不反着来。可用锁对象的 System.identityHashCode() 做排序依据;
  • 用 tryLock() 替代 synchronized:给锁加超时,拿不到就释放已持锁并重试或降级。注意要配合

    finally 解锁;
  • 缩小同步范围:把大段逻辑拆开,只锁真正共享修改的部分。例如“查库存→扣减→写日志”,只在扣减那一步加锁;
  • 用并发工具替代手写锁:优先选 ConcurrentHashMapAtomicIntegerStampedLockReentrantLock 的条件队列,它们内部已规避常见死锁模式。

预防比修复更重要

上线前做两件事能拦下 80% 死锁:

  • 代码评审时,专门问:“这里加了几个锁?顺序确定吗?有没有嵌套调用可能引入新锁?”;
  • 单元测试跑并发场景:用 CountDownLatch 同时启多个线程,模拟抢同一组资源,观察是否 hang 住;
  • 在 CI 流程里加 jstack 自动快照检查:启动应用后 sleep 2s,执行 jstack 并 grep “deadlock”,有就失败。

基本上就这些。死锁不复杂,但容易忽略锁的隐式传递和调用链变化。盯住顺序、超时、范围、工具这四点,99% 的问题都能压在上线前。