死锁是多个线程/事务因循环等待资源而互相阻塞,需同时满足互斥、占有并等待、非抢占、循环等待四条件;破坏任一条件即可预防,如统一锁序、用std::scoped_lock、按主键升序访问、重试事务、避免跨对象同步调用等。

死锁不是“程序卡了”,而是多个线程/事务在资源争夺中互相锁死、谁也不让步——只要四个条件同时成立,它就必然发生;破坏其中任意一个,就能防住。
为什么 pthread_mutex_lock 套用两把锁就容易死锁?
这是最典型的循环等待场景:线程 A 先锁 mutex_a,再尝试锁 mutex_b;线程 B 却反着来,先锁 mutex_b 再锁 mutex_a。一旦时序凑巧,A 拿着 a 等 b,B 拿着 b 等 a,立刻僵住。
- 别靠“运气”控制加锁顺序——必须全局约定一致的锁获取顺序,比如始终按地址大小、或按资源 ID 数值升序加锁
- 避免在持有锁期间调用可能阻塞或申请新锁的函数(如
malloc、printf、数据库查询) - C++ 中慎用
std::lock_guard包裹多个互斥量,它不保证顺序;改用std::scoped_lock(C++17 起),它内部做了死锁安全的原子加锁
数据库事务里 SELECT ... FOR UPDATE 怎么触发死锁?
MySQL/PostgreSQL 在行级锁场景下,死锁往往悄无声息地发生在高并发更新同一张表的不同行时。比如事务 1 更新用户 A 后想更新用户 B,事务 2 却先更新 B 再更新 A——底层锁管理器检测到环路,直接挑一个事务回滚并抛出 Deadlock found when trying to get lock。
- 尽量缩短事务生命周期:查完就更新,不要在事务里做 HTTP 请求、日志写入等耗时操作
- 所有涉及多行更新的业务,强制按主键/索引字段升序访问(例如
WHERE id IN (10, 5, 8)改成WHERE id IN (5, 8, 10)) - 应用层捕获死锁异常后,应重试事务(最多 2–3 次),而不是记录错误就放弃——数据库本身不保证重试逻辑
Java 里 synchronized 嵌套调用为啥也危险?
表面看是“同一个对象锁”,但若方法 A synchronized(this),内部调用了另一个对象 B 的 synchronized methodB(),而 B 的 methodB 又反过来调用了 A 的某个同步方法——这就构成了跨对象的循环等待,JVM 照样无法解开。
- 避免在 synchronized 方法内直接调用外部对象的同步方法;优先拆成无锁逻辑 + 显式锁协调
- 使用
java.util.concurrent.locks.ReentrantLock替代synchronized,因为它支持tryLock(timeout)——超时失败可主动释放已持锁,打破“持有并等待”条件 - 线程 dump(
jstack <pid></pid>)里一旦看到waiting to lock (a xxx)<br>& locked (a yyy)这类交叉描述,基本就是死锁现场
真正难防的不是教科书式的双锁嵌套,而是那些分散在不同模块、不同团队代码里的隐式依赖——比如服务 A 的 RPC 接口内部锁了资源 X,而服务 B 在回调里又试图锁 X;这种跨进程的等待环,监控和复现都极困难,得靠链路追踪+资源访问契约来约束。










