Note

怎么预防和解决死锁?

从破坏死锁四条件出发,说明预防、避免、检测恢复,以及工程上常用的加锁次序与超时策略。

省流

处理死锁通常分几类思路:预防(破坏四个必要条件之一)、避免(分配前判断是否进入不安全状态,如银行家算法)、检测与恢复(允许发生,发现后再解除)、以及工程上最常用的 加锁纪律(统一锁顺序、缩小临界区、尽量避免嵌套锁、使用超时或 try-lock)。

业务代码里最有效、也最常被考察的做法是:所有线程按相同的全局顺序获取多把锁,从结构上消灭循环等待;同时减少锁持有时间,降低“占有并等待”的窗口。数据库场景则常见死锁检测,发现后回滚其中一个事务。

没有单一银弹。选择取决于资源是否可抢占、能否接受回滚、以及检测成本。回答时应先提四条件,再对应到可落地的手段。

一、破坏必要条件(预防)

条件可能手段代价/限制
互斥改成无锁数据结构或只读共享并非所有资源都能去掉互斥
占有并等待一次性申请全部所需资源;或申请前先释放已持有资源利用率下降,实现复杂
不可抢占允许抢占/回滚已占用资源需要可撤销的状态与恢复协议
循环等待全局资源编号,按编号升序加锁要能给锁定义稳定顺序

其中 统一加锁顺序 是服务端开发面试中最实用的答案。

二、死锁避免

在分配资源前判断:即便满足当前请求,是否仍存在一条能使所有进程完成的分配序列。银行家算法是教材经典例子。它要求事先知道最大需求,实际通用业务系统中直接照搬较少,但“只在安全状态下分配”的思想仍有参考价值。

三、检测与恢复

允许死锁偶尔发生,再处理:

  1. 检测:维护等待图,查找环;数据库 InnoDB 等会做死锁检测。
  2. 恢复:回滚代价较低的一方、终止进程、或撤销部分资源占用后重试。

适合事务本身已支持回滚、且死锁不极频繁的场景。

四、工程实践清单

  • 固定锁顺序;文档化“必须先 A 后 B”。
  • 能合并就用一把锁或更粗粒度资源,避免无意义的锁嵌套。
  • 缩小临界区,锁内不做慢 I/O。
  • 使用 try_lock 或带超时的加锁,失败则释放已持有锁并退避重试,降低永久卡住概率。
  • 优先用现成并发组件(队列、channel、读写锁的正确模式),减少手写多锁协议。
  • 监控与日志:记录持锁时间、等待图或超时事件,便于确认是否真是死锁。

和“活锁、饥饿”分开说

  • 活锁:一直在重试,状态不断变化,但无进展。
  • 饥饿:某些线程长期得不到调度或锁。
  • 死锁:等待环导致所有相关方停止推进。

对策不同,面试里不要混用术语。