八、活锁 & 死锁

一、活锁(liveLock)

活锁是指线程间资源冲突激烈,引起线程不断的尝试获取资源,不断的失败。活锁有点类似于线程饥饿,虽然资源并没有被别人持有,但由于各种原因而无法得到。最常见的原因是进程组的执行顺序不合理,导致某些先需要的资源被后置。活锁和死锁的不同在于,活锁的状态是变化的,只是无法达到目的。活锁有可能在一定时间后自动解开,但死锁不能。

二、死锁(Deadlock)

死锁是指在一个并发系统中,多个线程或进程因争夺系统资源而造成的相互等待的一种状态。简言之,就是两个或多个进程或线程无限期地阻塞等待对方占有的资源。死锁是一种常见的并发问题,可能会造成系统资源的浪费和性能下降。
举个简单的例子:

假设有两个程序员 A 和 B 同时需要使用打印机来进行打印学习资料。程序员 A 先占用了打印机不释放,程序员 B 先拿到了电子学习资料不释放。此时:

程序员 A 说:你先给我学习资料,我打印完了就给你打印机
程序员 B 说:不行,你先给我打印机,我打印完了就给你学习资料 此时两个程序员A B就陷入了无限期的互相等待,形成了死锁。

在这个例子中,打印机和文件都是一种资源,程序员 A 和程序员 B 相当于两个线程,每个线程需要占用对方所需的资源才能完成操作。

经典死锁问题:哲学家就餐问题

file

通过上述例子,我们可以归纳出死锁产生的四个必要条件(缺一不可):

  1. 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  2. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  4. 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样 就形成了一个等待环路

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。
其中最容易破坏的就是 “循环等待”,因此我们最常用的一种阻止死锁的的技术就是“锁排序”,这种方式就可以通过破坏循环等待,阻止死锁发生。

比如还是上面的哲学家就餐,如果规定只能先拿标号比较小的筷子,那么死锁问题自然就不会发生了:

file