Java高频面试之并发编程-28
hello啊,各位观众姥爷们!!!本baby今天又来报道了!哈哈哈哈哈嗝🐶
面试官:线程死锁了解吗?该如何避免?
线程死锁的原理及避免方法
线程死锁是多线程编程中因资源竞争导致的僵局,多个线程互相持有对方所需资源且不释放,导致所有线程永久阻塞。理解其原理并采取预防措施是避免死锁的关键。
一、死锁产生的四个必要条件
- 互斥条件(Mutual Exclusion)
资源一次只能被一个线程占有(如锁、文件句柄等独占资源)。 - 占有且等待(Hold and Wait)
线程在持有至少一个资源的同时,等待获取其他线程占有的资源。 - 不可抢占(No Preemption)
资源只能由持有线程主动释放,不能被其他线程强行抢占。 - 循环等待(Circular Wait)
存在一个线程等待链,每个线程都在等待下一个线程持有的资源。
二、死锁示例
// 线程 A
synchronized (lock1) {Thread.sleep(100);synchronized (lock2) { ... }
}// 线程 B
synchronized (lock2) {Thread.sleep(100);synchronized (lock1) { ... }
}
两个线程以不同顺序获取 lock1
和 lock2
,可能进入死锁状态。
三、避免死锁的常用方法
1. 破坏“占有且等待”条件
- 一次性申请所有资源:线程在运行前申请所有需要的资源,若无法满足则等待。
// 通过一个全局锁一次性获取所有资源 synchronized (globalLock) {synchronized (lock1) {synchronized (lock2) { ... }} }
- 适用场景:资源需求明确且数量固定。
2. 破坏“不可抢占”条件
- 超时释放:尝试获取锁时设置超时,失败后释放已有资源并重试。
ReentrantLock lock1 = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock();// 尝试获取锁,超时后释放 if (lock1.tryLock(1, TimeUnit.SECONDS)) {try {if (lock2.tryLock(1, TimeUnit.SECONDS)) {try { ... } finally { lock2.unlock(); }}} finally { lock1.unlock(); } }
- 工具支持:使用
ReentrantLock
的tryLock()
方法。
3. 破坏“循环等待”条件
- 固定资源申请顺序:所有线程按相同顺序获取资源。
// 统一先获取 lock1,再获取 lock2 synchronized (lock1) {synchronized (lock2) { ... } }
- 哈希排序法:根据资源唯一标识(如哈希值)排序后再申请。
Object[] locks = { lock1, lock2 }; Arrays.sort(locks); // 按哈希值排序synchronized (locks[0]) {synchronized (locks[1]) { ... } }
4. 资源分配算法(预防死锁)
- 银行家算法:
系统预判资源分配是否会导致死锁,仅在安全状态下分配资源。- 实现复杂度高,多用于理论场景,实际开发中较少直接使用。
四、死锁检测与恢复
1. 检测方法
- 监控工具:
jstack
:生成线程转储,分析线程堆栈中的锁持有和等待关系。- VisualVM、JConsole:图形化工具查看线程状态。
- 日志分析:记录资源申请和释放日志,分析潜在死锁链。
2. 恢复策略
- 资源抢占:强制释放某线程持有的资源(可能导致数据不一致)。
- 线程终止:终止部分线程以打破循环等待(需谨慎处理业务状态)。
五、实际开发中的最佳实践
- 减少锁粒度:使用细粒度锁(如
ConcurrentHashMap
分段锁)。 - 减少锁持有时间:尽快释放锁,避免在锁内执行耗时操作(如 I/O)。
- 避免嵌套锁:尽量使用单一锁,或按固定顺序获取多个锁。
- 使用高层并发工具:
Executor
框架管理线程池。CountDownLatch
、CyclicBarrier
替代显式锁。- 使用无锁数据结构(如
AtomicInteger
、Disruptor
)。
六、总结
避免死锁方法 | 核心思想 | 适用场景 |
---|---|---|
固定资源顺序 | 破坏循环等待条件 | 多锁嵌套场景 |
超时释放 | 破坏不可抢占条件 | 高竞争、复杂同步逻辑 |
一次性申请资源 | 破坏占有且等待条件 | 资源需求明确且固定 |
无锁编程 | 避免互斥条件 | 计数器、状态标志等简单操作 |
关键原则:
- 早发现:通过代码审查和工具监控预防潜在死锁。
- 早规避:在设计和编码阶段采用固定顺序、超时等策略。
- 简化逻辑:减少不必要的同步,优先使用线程安全库。
你想要的技术资料我全都有:https://pan.q删掉汉子uark.cn/s/aa7f2473c65b