🏠 首页 › 攻略 › 死锁是什么?两个线程互相等对方放手的死循环

死锁是什么?两个线程互相等对方放手的死循环

死锁(Deadlock)是并发编程里最头疼的bug:两个线程各拿一把锁,都在等对方先放手,结果谁也动不了,程序卡死。本文用大白话讲清死锁的成因、四个条件和四种解法,帮你彻底搞懂这个坑。

死锁到底是什么?

先讲个生活场景。

你和朋友开车去停车场,两辆车挤在一个窄巷子里。你的车头堵住出口,他的车头也堵住了。你想出去得让他先挪,他想出去你得先挪。结果俩车互相卡着,谁也动不了。

这就是 死锁(Deadlock)。在编程里,两个或多个线程各占着一把锁,都在等对方手里的锁,结果大家谁都拿不到,程序就这么卡死不动了。

死锁一旦触发,通常不会自己解开,程序要么一直挂着,要么你把进程干掉。线上服务要是中了这个招,那就是直接宕机。

死锁要同时满足 4 个条件

死锁不是随便来的,必须 4 个条件全齐了才会发生:

条件意思
互斥一把锁同一时刻只给一个线程用
占有并等待线程拿着自己的锁,还去等别的锁
不可剥夺锁只能自己主动放,别人抢不走
循环等待A等B、B等A,形成闭环

缺任何一个,死锁就发不了。你拆掉"循环等待"这一环,死锁立刻解除。所以所有解法,本质都是在破坏这四个条件里的某一个。

4 种解法,各拆一个条件

1. 固定加锁顺序(拆循环等待)

让所有线程都按同一个顺序拿锁。比如都先拿 A 锁再拿 B 锁。谁都不许反过来,闭环就断了。这是最常用、最推荐的做法,简单又可靠。

2. 设超时,拿不到就放手(拆不可剥夺)

拿锁的时候加个超时时间。比如 5 秒还拿不到,就先把已经拿到的锁也放下,重新排队。线程能自己放弃资源,循环就断了。

3. 一次把所有锁拿齐(拆占有并等待)

别拿着一个锁去要另一个。要么一次性申请全部需要的锁,要么全放下。这样就不会"占着不放"。

4. 减小锁粒度(缓解互斥)

锁的范围越大,越容易和别人撞车。把大锁拆成小锁,每个线程只管自己那一小段,竞争的面变小了,死锁概率自然下降。

实际开发里怎么做

写多段代码时记住三件事:

第一,锁的顺序团队内要统一。哪怕只是约定俗成,也远好过各写各的。

第二,能用无锁结构就别硬上锁。比如用 ConcurrentHashMap、原子变量、通道这些,从根上绕开死锁。

第三,上线前用压测或死锁检测工具扫一遍。JVM 有 jstack 能抓线程栈,Linux 有 ftrace,很多语言也有内置的死锁检测器。别等线上出事才发现。

死锁不是高深的概念,核心就是"互相等、谁也动不了"。理解了 4 个条件,解法自然水到渠成。

下次代码莫名其妙卡住了,先别慌,看看是不是两个线程各占一把锁在互相等着——十有八九就是它。