死锁发生条件

  1. 互斥,共享资源 X 和 Y 只能被一个线程占用;

  2. 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资
    源 X;

  3. 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;

  4. 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是
    循环等待。

如何避免死锁

只要破坏4个条件中的一个即可避免死锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	
1. 对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待
了。
2. 对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不
到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。
3. 对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是
有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性
化后自然就不存在循环了。
我们已经从理论上解决了如何预防死锁,那具体如何体现在代码上呢?下面我们就来尝试用
代码实践一下这些理论。
1. 破坏占用且等待条件
从理论上讲,要破坏这个条件,可以一次性申请所有资源。在现实世界里,
转账操作,它需要的资源有两个,一个是转出账户,另一个是转入账户,当这两个
账户同时被申请时,我们该怎么解决这个问题呢?
可以增加一个账本管理员,然后只允许账本管理员从文件架上拿账本,也就是说柜员不能直
接在文件架上拿账本,必须通过账本管理员才能拿到想要的账本。例如,张三同时申请账本
AB,账本管理员如果发现文件架上只有账本 A,这个时候账本管理员是不会把账本 A
拿下来给张三的,只有账本 AB 都在的时候才会给张三。这样就保证了“一次性申请所
有资源”。

关于避免死锁

1
2
3
4
5
6
7
8
9
10
11
12

1. 避免滥用锁,程序里用的锁少,写出死锁 Bug 的几率自然就低。
2. 对于同一把锁,加锁和解锁必须要放在同一个方法中,这样一次加锁对应一次解锁,代
码清晰简单,便于分析问题。
3. 尽量避免在持有一把锁的情况下,去获取另外一把锁,就是要尽量避免同时持有多把
锁。
4. 如果需要持有多把锁,一定要注意加解锁的顺序,解锁的顺序要和加锁顺序相反。比
如,获取三把锁的顺序是 A、B、C,释放锁的顺序必须是 C、B、A。
5. 给你程序中所有的锁排一个顺序,在所有需要加锁的地方,按照同样的顺序加解锁。比
如我刚刚举的那个例子,如果两个线程都按照先获取 lockA 再获取 lockB 的顺序加
锁,就不会产生死锁。