死锁典型模式是多线程以不同顺序获取同一组锁,导致相互等待;预防需统一加锁顺序、使用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,让你有机会清理资源并退出。