什么是死锁,如何避免,发生后如何解除或恢复?

考查重点:资源等待环与恢复策略。互相持资源等对方形成环;统一顺序和缩短持锁预防,发生后检测并终止/回滚一方,重试需幂等

本题目录

参考答案

核心回答

死锁是一组线程、进程或事务相互等待对方持有的资源,导致这些参与者都无法继续推进。例如 T1 拿着锁 A 等锁 B,T2 拿着锁 B 等锁 A,双方都到不了释放锁的位置。

处理分为三个层次:事前通过统一资源获取顺序等方式预防;运行时找到实际等待关系;发生后让某个参与者取消、回滚或退出,打破等待环,并恢复业务状态。 数据库通常能回滚一个事务解锁,普通线程却不一定有安全的强制解锁手段,二者不能混为一谈。

死锁为什么会发生

经典资源死锁需要同时具备四个必要条件:

  • 互斥:某些资源不能被同时占用。
  • 占有并等待:已经持有资源,还继续等待其他资源。
  • 不可抢占:已占用资源不能被外部随意剥夺,需要持有者释放。
  • 循环等待:参与者之间形成循环等待关系。

破坏任一必要条件,就能阻止相应的资源死锁。普通锁竞争可能只是等待持有者执行完;饥饿是某个参与者长期轮不到;活锁则是参与者反复重试、让步,但没有实际进展。接口很慢、CPU 很高或很低,都不能独立证明死锁。

如何预防和避免

统一加锁顺序最实用。 例如转账同时修改两个账户时,所有入口都按稳定且唯一的账户 ID 从小到大取得锁,而不按“先付款方、后收款方”取锁。相等标识或同一对象要单独处理,其他调用链也必须遵守这个顺序。

拿不齐就退出,并释放已拿到的资源。 可以设计一次申请所需资源,或获取下一份资源失败时放弃本次尝试。在 Java 中,可用支持超时的 tryLock,配合明确的清理逻辑,只释放当前线程已经成功取得的锁。超时后仍抱着原有锁重试,等待环就没有真正解除。

缩短持锁范围,减少不必要的嵌套。 尽量避免持锁调用不受控回调、远程接口或等待其他任务;需要移到锁外的工作,也要考虑状态快照和提交时的重新校验,不能为缩短锁时间而破坏一致性。关系紧密的状态有时用一把锁更容易保证正确,但会降低并发度。

严格的操作系统术语中,预防是从结构上破坏条件;避免是分配资源前判断是否仍处于安全状态,例如银行家算法。后者需要已知最大资源需求等条件,不是给任意业务代码加一个超时就实现了银行家算法。

发生后怎样定位

先确定等待的是哪一种资源,整理“谁持有、谁等待、在哪条调用链取得”的关系。

Java 场景可以抓线程栈和锁信息,例如通过 jcmd 的 Thread.print -l 或 jstack -l,顺着等待锁找到持有者,再检查持有者在等待什么。ThreadMXBean.findDeadlockedThreads() 可以帮助检测受支持的平台线程监视器与 ownable synchronizer 等待环,但不能覆盖所有跨数据库、网络或任务依赖的等待,Java SE 25 的该方法也不检测含虚拟线程的环。

数据库场景则结合死锁日志、事务、持有和申请的锁、索引与 SQL 执行顺序分析。MySQL 的最近死锁信息与当前锁等待视图用途不同:当前没有等待,可能只是数据库已经回滚了一个受害事务,不能因此否定之前发生的死锁。

对于每类只有一个实例的不可抢占资源,实际阻塞形成的等待环可以判定死锁;多实例资源还要判断是否有其他可用实例或参与者可以先完成。跨层等待则需要把应用栈、数据库锁和业务任务关系连起来。

如何解除和恢复

数据库事务:回滚受害者,重新执行事务。 例如 InnoDB 在启用死锁检测时可以选择一个事务回滚,释放它的锁。应用收到死锁错误后,应重新读取状态并重跑整个被回滚事务,采用有上限的重试与退避;不能只补最后一条 SQL。事务外已经发出的远程请求或消息不会随数据库自动撤销,相关业务效果还需要幂等或补偿。

有协作取消能力的线程:取消等待,清理后退出。 如果代码使用 lockInterruptibly 或可中断的定时锁等待,中断可以让等待抛出异常,随后由业务代码释放已持有的资源、回滚本次工作。中断不是强制解锁指令;等待进入 synchronized 的线程,不能靠一次 interrupt 自动退出并释放已有锁。

没有安全退出路径:隔离影响,再恢复进程或实例。 普通线程可能已经把内存或外部状态修改了一半,不能由另一个线程随意代它解锁,也不能依赖强行终止线程来保持一致性。必要时保存诊断信息、停止新流量并重启受影响实例,之后检查未完成任务及外部副作用,再补偿或重试。重启解决当前卡住,不会修复反向加锁等根因。

常见追问

设置超时就保证不会死锁了吗?

超时必须配合取消、释放已持有资源和业务回退才有效。外层请求超时后,内部线程若仍持锁等待,死锁还在;所有线程按相同节奏立即重试,也可能产生持续冲突或活锁。

换成 ReentrantLock 或公平锁就能避免吗?

可重入解决同一线程重复获取同一把锁的问题,公平性影响竞争次序;它们都不会自动消除多把锁之间的循环等待。需要利用锁提供的能力落实顺序、超时和取消策略。

如何减少数据库里的死锁?

统一访问多行、多表的顺序,缩短事务,建立合适索引以减少扫描和锁定范围,并避免不必要的锁定读取。仍应保留死锁重试处理,不能把降低概率当作永不发生。

参考资料

  • 国内:JavaGuide:死锁详解:借鉴等待关系、四个条件、资源实例数和恢复方式,区分正文中的阻塞与死锁,并解释数据库回滚与线程退出的业务恢复边界。
  • 国内:JavaGuide:Java 并发常见面试题总结(上):采用两把锁反向获取的例子与预防、避免、安全状态的说明,解释正文中统一加锁顺序与安全性判断。
  • 国内:阿里云:RDS MySQL 锁分析:参考最近、历史死锁和当前阻塞的区分,说明正文中锁等待视图、事务执行顺序与历史日志应怎样配合定位。
  • 国外:Java SE 25:ReentrantLock:依据可中断、超时获取与释放锁的约定,解释正文中协作取消和持有者释放锁的边界。
  • 国外:MySQL 8.4:How to Minimize and Handle Deadlocks:短事务、固定访问顺序、索引与事务重试的说明,用于把数据库死锁恢复连接到应用层的完整重试和幂等处理。

相关面经

7 篇

网易雷火

游戏服务端相关岗位
一面
查看原面经 →
你在实习过程中有没有遇到过死锁的问题,是怎么解决的?

科大讯飞、首都在线、宇量晟、四川长虹、度小满、字节跳动、恒生电子、腾娱互动、金蝶、汽车之家

面经合集
查看原面经 →
重返帝国项目组。八股多,死锁成因以及解决方案,mysql怎么解决死锁,查看死锁命令有哪些,jvm调优思路,手撕链表,以及一些基础的操作链表方法。一共一个小时。

腾讯云

后台开发工程师
日常实习一面
查看原面经 →
4.2 知道死锁吗?
追问
4.3 你举的这个例子怎么解决呢?

蚂蚁集团

面经合集
查看原面经 →
死锁是什么?怎么避免

蚂蚁集团

面经合集
查看原面经 →
死锁的成因及解决办法。
展开剩余 2 篇面经

美团

软件开发工程师
秋招一面
查看原面经 →
什么是死锁?
怎么样避免产生死锁?还有别的吗?
如果发生了死锁,要怎么解除或恢复?

字节跳动

AI 应用开发工程师
日常实习一面
查看原面经 →
什么是死锁?
追问
一般怎么解决死锁问题?

收录题集

2 个来源,共2 份题集

操作系统高频知识点

Rocky Ding · AIGC-Interview-Book · 1 条原题

查看题集 →

工程化踩坑:死循环、状态丢失与成本控制

Onefly · zero2Agent · 1 条原题

查看题集 →

相关题目

4 题