多线程死锁
通过一个例子引入死锁的概念。
古代钱庄,通过手写记账记录账户余额,现在客户A用一个账本A,客户B有个账本B,这些账本统一放在文件架上。
现在来了一个客户 需要从A账户装500到B账户。这个时候柜员拿账本的时候会有以下情况:
- A,B账本都在文件架上,柜员全部拿走。
- 文件架上只有A账本,柜员拿走,并且等待其他柜员把B账本送过来。
- 转出账本A与转入账本B都不在文件架上,柜员等待其他柜员送过来。
上面的逻辑可以理解为把A账户作为一个锁,B账户作为一个锁,同时拿到两个锁才可以执行。
引入死锁
有一种场景,当一柜员拿到A账本后,等待其他柜员把B账本送过来。但是另一个柜员此时拿着B账本,等到其他柜员把A账本送过来。此时就造成了死锁。
死锁的定义
一组相互竞争资源的线程因互相等待,导致‘永久’阻塞的现象。
死锁的条件
只有以下4个条件都发生的时候才会出现死锁(注意是都发生)
- 互斥:共享资源A和B只能被同一个线程占用。A和B就是上面的账本。
- 占有且等待: 线程T1已经取得共享资源A,在等待共享资源B的时候,不会释放贡献支援A.
- 不可抢占: 其他线程不可以抢占线程T1占有的资源。
- 循环等待:线程T1等待线程T2的资源释放,线程T2等待线程T1的资源释放。
反过来分析就是,破坏其中的一个条件就能避免死锁。
- 互斥是锁的特性,不能修改。
- 占有且等待:等拿到两把锁的时候再去执行代码。引用上面的例子,就是当A账本和B账本都在文件架上的时候,才把账本交给柜员。 在代码中可以把拿到的锁对象放到list中,校验是否包含。也可以通过自旋的形式去获取两把锁。
- 不可抢占锁:可以使用等待唤醒机制。
- 破坏循环条件:大致意思就是,两个柜员都去拿A,B账本,我们需要按照一定的顺序去取锁,两个柜员都先去找A账本,在去找B账本。这样就不会出现循环等待。在代码中就是拿到两个锁。把Id小的对象作为第一个锁,另外一个做为第二把锁。