死锁典型模式是多线程以不同顺序获取同一组锁,导致相互等待;预防需统一加锁顺序、使用tryLock超时机制及lockInterruptibly响应中断,并辅以jstack和ThreadMXBean检测。

死锁发生的典型代码模式
Java 中死锁最常见于多个线程按不同顺序获取同一组 ReentrantLock 或同步块(synchronized),比如线程 A 先锁 lockA 再等 lockB,而线程 B 正好相反。此时 JVM 不会主动检测或中断,程序卡住但无异常、无日志,只表现为线程状态长期停留在 BLOCKED。
用 jstack 可直接看到「found 1 deadlock」提示,并列出互相等待的线程栈 —— 这是定位的第一步。
如何预防:统一加锁顺序 + tryLock 超时
预防比排查更有效。核心原则是:所有线程以相同全局顺序申请锁。例如按对象哈希码排序:
if (System.identityHashCode(objA) < System.identityHashCode(objB)) {
synchronized (objA) {
synchronized (objB) { /* ... */ }
}
} else {
synchronized (objB) {
synchronized (objA) { /* ... */ }
}
}更稳妥的方式是用 ReentrantLock.tryLock(long, TimeUnit),超时后主动释放已持锁:
立即学习“Java免费学习笔记(深入)”;
- 避免无限等待,把死锁风险转为可捕获的业务逻辑分支
- 注意:必须在
finally块中调用unlock(),否则可能永久泄漏锁 - 超时时间不宜过短(如 10ms),否则高并发下易频繁失败;建议从 100ms 起调
使用显式锁时务必配合 lockInterruptibly
当线程可能被外部中断(如服务优雅停机),别用 lock(),改用 lockInterruptibly()。它会在等待锁时响应 Thread.interrupt(),抛出 InterruptedException,让你有机会清理资源并退出。
对比:
-
lock():无视中断,一直等到锁可用 -
lockInterruptibly():若等待中被中断,立即抛异常,不继续阻塞
这是避免“假死”而非真死锁的关键手段,尤其在容器环境(如 Spring Boot)中,中断信号常用于控制生命周期。
工具链辅助:JDK 自带诊断 + ThreadMXBean 编程检测
除了 jstack,还可通过 ThreadMXBean 在运行时编程检测:
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
long[] deadlockedIds = mxBean.findDeadlockedThreads(); // 返回 null 表示无死锁
if (deadlockedIds != null) {
ThreadInfo[] infos = mxBean.getThreadInfo(deadlockedIds, true, true);
// 打印或上报
}适合集成进健康检查端点(如 /actuator/health),但注意该方法有性能开销,不应高频调用。
真正难处理的不是明显双锁嵌套,而是跨多层调用、锁粒度不一致、或与数据库事务混用导致的隐式依赖 —— 这类场景里,锁顺序很难全局约束,得靠设计降级(如拆分操作、引入消息队列解耦)。










