死锁的条件与处理方法?

2026年 阅读约 undefined 分钟 undefined

死锁为什么必须同时满足四个条件?本文讲清互斥、占有等待、不可抢占、循环等待的定义,预防与避免的区别,银行家算法的安全性检查步骤,死锁检测与恢复,附 Java 死锁示例与 jstack 排查命令。

一句话总结

死锁是两个或多个进程互相等待对方持有的资源,谁都动不了。它必须同时满足四个条件:互斥、占有且等待、不可抢占、循环等待。处理思路分四层:预防(破坏必要条件,如一次性申请全部资源)、避免(银行家算法,动态检查分配后是否仍安全)、检测与恢复(画等待图找环,杀进程或回滚)、鸵鸟算法(不管,UNIX/Linux 的选择)。

初级理解

四个必要条件

条件含义日常例子
互斥资源一次只能被一个进程占用打印机、锁
占有且等待拿着已有资源不放,再去申请新资源握着筷子等汤勺
不可抢占别人不能强行抢走已分配的资源锁不会被自动释放
循环等待存在一个进程链,彼此等待A 等 B,B 等 C,C 等 A

四个条件缺一不可。所以只要破坏其中任意一个,死锁就不会发生。

最小死锁例子

# 线程 A # 线程 B lock(X); lock(Y); lock(Y); ← 阻塞等 B 释放 lock(X); ← 阻塞等 A 释放 # 结果:A 拿着 X 等 Y,B 拿着 Y 等 X,永远卡死 # 对策:全局统一加锁顺序(都先 X 后 Y),循环等待条件被破坏

中级深入

预防 vs 避免

# 预防(静态策略,从条件入手,限制多但彻底): # 1. 破坏"占有且等待":一次性申请所有资源,要么全给要么全不给 # 2. 破坏"不可抢占":申请新资源失败时,先释放已占有的再重试 # 3. 破坏"循环等待":资源编号,只允许按递增顺序申请 # 4. 破坏"互斥":让资源可共享访问(打印机用假脱机 SPOOLing) # 避免(动态策略,不限制申请方式,但每次分配前做安全判断): # 典型就是银行家算法:分配后系统仍处于"安全状态"才允许

银行家算法

# 数据结构(5 个进程 P0-P4,3 类资源 A/B/C): # Max 每个进程最多需要多少 # Allocation 已分配多少 # Need = Max - Allocation 还差多少 # Available 系统还剩多少 # 安全性检查步骤: # 1. Work = Available;Finish[i] = false # 2. 找 Need[i] <= Work 且未完成的进程 i # 3. 假设它跑完:Work += Allocation[i];Finish[i] = true;回第 2 步 # 4. 所有 Finish 都为 true → 安全状态;否则不安全,拒绝这次分配 # 特点:需要预知 Max(实际系统很少知道),理论价值大于工程价值

高级进阶

死锁检测

# 资源分配图:进程→资源(请求边),资源→进程(分配边) # 图里存在环 = 死锁(每类资源只有一个实例时成立) # 多实例资源用"等待图":只画进程之间的等待边,找环 # 检测时机:每次请求都查(开销大)/ 定时查 / CPU 利用率掉到阈值以下时查 # 检测到后的恢复手段: # 1. 杀进程:杀掉环上全部或逐个杀(牺牲品选择有讲究) # 2. 回滚:把进程恢复到之前的检查点,释放资源重来 # 3. 抢占:强行抢资源给别的进程(可能造成任务失败)

活锁与饥饿

# 活锁:进程没阻塞,但互相"礼让"导致谁都没进展 # 例:两个人走廊相遇,同时让同一边,反复几次还是撞上 # 解法:加随机退避(以太网冲突退避、锁竞争加 jitter) # 饥饿:进程一直得不到资源(不是死锁,别人一直在用) # 例:短作业优先下长作业永远排不上 # 解法: Aging(等待越久优先级越高)

实战场景

# 场景1:Java 死锁最小复现 Object x = new Object(), y = new Object(); new Thread(() -> { synchronized (x) { sleep(100); synchronized (y) {} } }).start(); new Thread(() -> { synchronized (y) { sleep(100); synchronized (x) {} } }).start(); # 定位:jstack PID 找到 "Found one Java-level deadlock" 段 # 修复:统一加锁顺序、或改用 ReentrantLock.tryLock(timeout) 带超时
# 场景2:数据库死锁 # 两个事务互相持有对方要的行锁 # InnoDB 自动检测(wait-for graph 找环),回滚代价小的事务 # 业务侧对策:事务尽量短、按固定顺序更新、加索引减少锁范围 # 排查:SHOW ENGINE INNODB STATUS\G 看 LATEST DETECTED DEADLOCK
# 场景3:分布式锁死锁 # 客户端拿到锁后宕机,锁永不释放 → 死锁 # 对策:锁带 TTL(自动过期)+ 续期(看门狗,Redisson 的实现) # 释放锁要校验持有者(Lua 脚本比对 value),避免误删别人的锁

面试模拟

面试官:死锁产生的四个条件?破坏一个就能避免吗?

你:互斥、占有且等待、不可抢占、循环等待。四个必须同时满足才会死锁,所以破坏任意一个就能预防。工程上最常用的是破坏循环等待——规定全局一致的加锁顺序;其次是避免占有且等待——一次性申请全部资源或用 tryLock 带超时。

面试官:银行家算法在做什么?

你:它是死锁避免的代表:每次分配前模拟分配后的状态,按安全性检查流程看是否还能找到一个让所有进程顺利完成的序列,找不到就拒绝这次分配。前提是要预先知道每个进程的最大需求,所以实际系统很少用,更多是操作系统课的经典模型。

面试官:死锁、活锁、饥饿的区别?

你:死锁是进程都阻塞了,互相等待永远无法推进;活锁是进程没阻塞但一直在互相退让,同样没有进展;饥饿是单个进程长期得不到资源,别人一直在用。死锁靠检测恢复,活锁靠随机退避,饥饿靠 Aging 提优先级。