java线程死锁
在Java中,死锁(Deadlock)是指两个或多个线程在执行过程中,因争夺资源而陷入互相等待的僵局。当以下四个必要条件同时满足时,就会发生死锁:
死锁产生的必要条件
互斥条件(Mutual Exclusion)
资源一次只能被一个线程占用(例如:synchronized
锁、数据库连接等独占资源)。请求与保持(Hold and Wait)
线程持有至少一个资源,同时请求其他线程占有的资源(不释放已持有资源)。不可剥夺(No Preemption)
线程已获得的资源在未使用完前,不能被强行剥夺(只能主动释放)。循环等待(Circular Wait)
存在线程资源的环形等待链:线程A
→ 等待线程B
的资源 →线程B
→ 等待线程A
的资源。
死锁示例代码
1 | public class DeadlockDemo { |
执行结果:
1 | ThreadA: Holding lock1... |
死锁原因分析
- 互斥:
lock1
和lock2
一次只能被一个线程占用。 - 请求与保持:
- 线程A持有
lock1
并请求lock2
。 - 线程B持有
lock2
并请求lock1
。
- 线程A持有
- 不可剥夺:JVM 不会强制回收线程已持有的锁。
- 循环等待:
线程A
→ 等待lock2
(被线程B持有)→线程B
→ 等待lock1
(被线程A持有)。
如何避免死锁?
打破任意一个必要条件即可预防死锁:
- 避免嵌套锁:尽量只获取一个锁。
- 固定锁的顺序:所有线程按相同顺序获取锁(例如先
lock1
后lock2
)。 - 使用超时机制:通过
Lock.tryLock(long timeout, TimeUnit unit)
尝试获取锁,超时则回退。 - 死锁检测:利用JDK工具(如
jstack
、JConsole
)监控线程状态。
修复后的代码(固定锁顺序):
1 | // 所有线程必须先获取lock1,再获取lock2 |
诊断死锁工具
- 命令行:
1
jstack <PID> # 打印线程栈,会标记"Found one Java-level deadlock"
- JConsole/VisualVM:图形化查看线程阻塞状态,检测死锁。
总结
必要条件 | 解决策略 |
---|---|
互斥 | 使用非阻塞数据结构(如ConcurrentHashMap ) |
请求与保持 | 一次性申请所有资源(原子操作) |
不可剥夺 | 使用可中断锁(Lock.lockInterruptibly() ) |
循环等待 | 固定资源申请顺序(最常用方案) |
通过合理设计锁的获取顺序和超时机制,可有效避免死锁问题。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 技术之路!
评论