死锁(deadlock)


  • 资源抢占问题

  • 什么是死锁?

  • 死锁产生的必要条件?

  • 如何解决死锁问题?


1、资源抢占问题

??假设进程A正持有资源S, 此时进程B也对资源S请求使用并且成功,同时进程B抢占资源后没有造成副作用,那么就可以说资源S是可抢占式资源。
??可抢占资源 假设系统内存为512MB和一台打印机。此时有进程A、进程B,这两个进程所需要的内存恰好也都是512MB。进程A首先申请到打印机,进程B申请到了512MB的内存。此时这两个进程都不会正常工作,因为进程A需要512MB的内存,该内存又恰好被进程B所占用;对于进程B虽然申请到了所需要的内存,但是没有申请到打印机,也不能正常打印。如果将进程B占用的内存换出,进程A换入内存中,那么进程A就能正常打印,之后进程B也能正常打印。这一过程中内存(储存器)就是可抢占资源。
??不可抢占资源 假设进程A正在执行光盘刻录任务,此时进程B也申请光盘刻录, 光盘刻录机此时是不会被分配给进程B的,因为这会对进程A的刻录产生损坏。这种情况下,光盘刻录机就是不可抢占资源(抢占了就会造成不好的后果)。

2、什么是死锁?

??对于可抢占资源中的进程A和进程B而言,内存是可抢占式资源。但是如果它们抢占的不是内存,而是一种不可抢占资源,又会有怎么样的结果?也就是说进程A持有资源S,进程B持有资源T,进程A的执行还要再持有资源T,进程B的执行也需要持有资源S(S、T都是不可抢占资源,它们都被某个进程占用),这就造成进程A与进程B均处在阻塞状态。这种情况就是死锁。此时进程A、进程B和资源S、资源T之间的示意图如下所示

3、死锁产生的必要条件?

?(1)互斥条件: 在某一段时间内,当进程A持有资源S时,进程A是排它性的占用该资源,也就说在进程A占用资源S的期间内,别的进程申请资源S只能先等待进程A释放资源S。
?(2)请求和保持条件: 进程A在已占用资源S的情况下,进程A还可以向别的资源发出申请,但依然占用资源S。
?(3)不可抢占条件: 在进程A未释放资源S期间内,资源S是一直被进程A所占用的,别的进程不能抢占,只能先等待进程A显式释放资源S。
?(4)环路等待条件: 当发生死锁时,系统内至少有2个进程形成了资源环路(类似死锁示意图所示)。在这个环内,任意一个进程\(P_{i}\)都在等待下一个进程\(P_{i+1}\)释放\(P_{i}\)需要的资源程\(S_{i}\)

4、如何解决死锁问题?

?(1)鸵鸟算法(忽视策略): 鸵鸟算法意思是直接忽略死锁问题。
?(2)死锁的检测与恢复(检测与恢复策略): 首先允许发生死锁问题,然后通过相关检测算法对死锁进行检测;当检测出现的死锁后,利用恢复技术进行恢复。相关的恢复方法有

?(3)分配资源前进行仔细的检查避免死锁(避免策略): 相关的检查方法有银行家算法,分配资源前先检测资源分配之后是否导致不安全的状态。缺点是银行家算法需要知道资源分配的数值,但资源值以及进程数一般都是运行时态才知道的。
?(4)破坏死锁的四个条件之一(预防策略):
???① 破坏互斥条件:破坏进程对某个资源的排它性占有。
???② 破坏请求和保持条件:直接分配给该进程所需的全部资源供其使用。其实如果事先知道了所需的全部资源是哪些,银行家算法也可以使用。
???③ 破坏抢占条件:有些资源可被虚拟化,然后就可以抢占被持有的资源且不发生会乱。
???④ 破坏环路条件:资源进行编号,严格按号来调度资源。

5、Java死锁的例子

public class DeadLock {
    public static void main(String[] args) {
        String res1 = "resource_1";
        String res2 = "resource_2";

        new Thread(new MyThread(res1, res2), "t1").start();
        new Thread(new MyThread(res2, res1), "t2").start();
    }
}

class MyThread implements Runnable {
    String res1;
    String res2;

    public MyThread(String res1, String res2) {
        this.res1 = res1;
        this.res2 = res2;
    }

    @Override
    public void run() {
        // 首先获取一个资源
        synchronized (res1) {
            System.out.printf("%s: %s, wangting --> %s\n", Thread.currentThread().getName(), res1, res2);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 再申请另一个资源
            synchronized (res2) {
                System.out.printf("%s: %s, wangting --> %s\n", Thread.currentThread().getName(), res2, res1);
            }
        }
    }
}