操作系统深入学习-----死锁及死锁的解决策略(附Java示例demo)

一.从一道面试题说起

请你说一下如何解决死锁?

  • 如果你第一时间想到的是下面的回答:
  • 破坏死锁的四个必要条件之一,即互斥,请求和保持,不可剥夺,循环等待,然后逐句解释这四个条件含义和如何破坏。。。

这个答案没有问题,但是不够准确也不够全面


二.死锁概念-什么是死锁?

1.定义

在多道程序环境下,多个进程可能竞争一定数量的资源,某个进程申请资源时,资源不可用,那么该进程进入等待状态,如果申请的资源被其他进程占有,那么该等待进程可能无法改变其状态,这种情况称之为死锁(Dead Lock) --------------------------------------《操作系统概念》

简单理解:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去

在这里插入图片描述

2.原理

进程使用资源的顺序

  1. 申请:进程请求资源。如果申请不能立即被允许(例如,申请的资源正在被其他进程使用),那么申请进程应等待,直到它能获得该资源为止。
  2. 使用:进程对资源进行操作(例如,如果资源是打印机,那么进程就可以在打印机上打印了)。
  3. 释放:进程释放资源。

当进程或线程每次使用内核管理的资源时,操作系统会检查以确保该进程或线程已经请求并获得了资源。系统表记录每个资源是否是空闲的或分配的。对于每个已分配的资源,该表还记录了它被分配的进程。如果进程申请的资源正在为其他进程所使用,那么该进程会添加到该资源的等待队列上。

当一组进程内的每个进程都在等待一个事件,而这一事件只能由这一组进程的另一个进程引起,那么这组进程就处于死锁状态。 这里所关心的主要事件是资源的获取和释放。资源可能是物理资源(例如,打印机、磁带驱动器、内存空间和 CPU 周期)或逻辑资源(例如,信号量、互斥锁和文件)。然而,其他类型的事件也会导致死锁(例如 IPC 功能)


3.特征/条件

必要条件
如果在一个系统中以下四个条件同时成立,那么就能引起死锁:

  1. 互斥:至少有一个资源必须处于非共享模式,即一次只有一个进程可使用。如果另一进程申请该资源,那么申请进程应等到该资源释放为止。
  2. 占有并等待:—个进程应占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有。
  3. 非抢占:资源不能被抢占,即资源只能被进程在完成任务后自愿释放。
  4. 循环等待:有一组等待进程 {P0,P1,…,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,……,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。
    我们强调所有四个条件必须同时成立才会出现死锁。循环等待条件意味着占有并等待条件,这样四个条件并不完全独立。

三.死锁程序Demo

代码:

public class DeadLockDemo {

    public static void main(String[] args) {

        String lock1 = "lock1";
        String lock2 = "lock2";

        new Thread(()->{
            synchronized (lock1){
                System.out.println(Thread.currentThread().getName()+"获取lock1,尝试获取lock2");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println(Thread.currentThread().getName()+"获取lock1,lock2");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (lock2){
                System.out.println(Thread.currentThread().getName()+"获取lock2,尝试获取lock1");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1){
                    System.out.println(Thread.currentThread().getName()+"获取lock1,lock2");
                }
            }
        },"B").start();
    }
}

运行效果:
在这里插入图片描述
可以看出两个线程互相请求对方所持有的资源(对象)导致死锁。


  1. 思路:利用Synchronized关键字锁住目标对象,通过嵌套的语句对两个资源(Object)先后进行请求,同时新建两个线程就能观察到死锁现象。
  2. 检测方法:在Terminal中输入Jps -l 查看所有进程。然后通过jstack指令查看当前线程堆栈状态,结果如下:
    在这里插入图片描述

四.死锁的解决办法有哪些


一般来说,处理死锁问题有三种方法:

  1. 通过协议来预防或避免死锁,确保系统不会进入死锁状态。
  2. 可以允许系统进入死锁状态,然后检测它,并加以恢复。
  3. 可以忽视这个问题,认为死锁不可能在系统内发生。

注意,这个策略是在操作系统内核层面上而言的,第三种解决方案为大多数操作系统所采用,包括 Linux 和 Windows。但是我们在上层业务中必须考虑到可能出现的死锁状况。

死锁预防----破坏必要条件
  1. 破坏互斥? 对于共享的资源没问题,但是有些资源本身不是共享的,所以大多数情况下不会破坏这个条件
  2. 破坏请求和保持?可以在进程运行之前,提前对所有进程进行资源的分配,如果有进程分配不到对应资源,就释放掉它本身分配到的资源。但是有两个问题,一资源利用率低,二是饥饿,假如一个进程要求资源比较多,那么它很有可能永远不会被执行
  3. 破坏不可剥夺? 当一个进程获取不到对应资源,则释放掉其已有的资源
  4. 破坏循环等待条件?
    • 可以指定进程获取资源的顺序,是最常用的预防方法。
    • 另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(译者注:加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)。

为了确保死锁不会发生,系统可以采用死锁预防或死锁避免方案。死锁预防方法确保至少有一个必要条件不成立。这些方法通过限制如何申请资源的方法来预防死锁。

死锁恢复----恢复的策略
  1. 人工处理
  2. 进程终止:
    • 终止所有死锁进程:开销大,消耗资源多
    • 一个一个终止进程,直到死锁现象消失:开销大,每次终止进程,都需要调用死锁检测算法。
  3. 资源抢占:从死锁的环结构中抢占资源,直到其循环被打破
    • 选择牺牲品:抢占谁的资源,这需要综合考虑
    • 回滚:必须将被抢占资源的进程回滚到安全状态
    • 饥饿:同一个进程所持有的资源很可能每次都被抢占

五.总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值