文章目录

  • 1、预防死锁
  • 1.1、破坏互斥条件
  • 1.2、破坏不剥夺条件
  • 1.3、破坏请求和保持条件
  • 1.4、破坏循环等待条件
  • 2、避免死锁(银行家算法)
  • 2.1、什么是安全序列
  • 2.2、安全序列、不安全状态、死锁的联系
  • 2.3、银行家算法
  • 2.4、Java 实现银行家算法
  • 2.5、总结
  • 3、死锁的检测
  • 3.1、Java 定义该数据结构(资源分配图)
  • 3.2、死锁检测的流程
  • 3.3、死锁检测的算法
  • 3.4、 Java 实现死锁检测的算法
  • 3.5、死锁定理
  • 4、死锁的解除
  • 4.1、资源剥夺法
  • 4.2、撤销进程法(终止进程法)
  • 4.3、进程回退法
  • 4.4、如何决定 "对谁动手"
  • 5、整体框架



select for update死锁springboot怎么解决 spooling解决死锁问题_死锁

1、预防死锁

select for update死锁springboot怎么解决 spooling解决死锁问题_互斥_02

1.1、破坏互斥条件

互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁。

如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。

  • 比如:SPOOLing 技术。操作系统可以采用 SPOOLing 技术把独占设备在逻辑上改造成共享设备。
  • 比如,用 SPOOLing 技术将打印机改造为共享设备…

select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_03

如上右所示:进程 1 和进程 2 将作业交给输出进程即可,不需要阻塞,具体的打印操作由输出进程操作即可

  • 可以理解为菜鸟驿站

该策略的缺点:并不是所有的资源都可以改造成可共享使用的资源

  • 并且为了系统安全,很多地方还必须保护这种互斥性。
  • 因此,很多时候都无法破坏互斥条件。

1.2、破坏不剥夺条件

不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。

破坏不剥夺条件:

① 方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。

② 方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)


该策略的缺点

  • 实现起来比较复杂
  • 释放已获得的资源可能造成前一阶段工作的失效。
  • 因此这种方法一般只适用于易保存和恢复状态的资源,如 CPU
  • 反复地申请和释放资源会增加系统开销,降低系统吞吐量。
  • 若采用方案一,意味着只要暂时得不到某个资源、之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿。

1.3、破坏请求和保持条件

请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。

可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。


该策略实现起来简单,但也有明显的缺点:

  • 有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。
  • 另外,该策略也有可能导致某些进程饥饿。

如下所示:若有源源不断地 A 类进程进来,资源 1 一直被分配给 A 类进程,会导致 C 类进程饥饿

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1cRpiWmd-1676912758827)(https://gitcode.net/qq_67720621/img/-/raw/master/typora-user-images-new/image-2023020520384541png)]

1.4、破坏循环等待条件

循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。

可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。

原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。

select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_04


该策略的缺点:

  • 不方便增加新的设备,因为可能需要重新分配所有的编号;
  • 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费;

例如:5 号资源为打印机,7 号资源为扫描仪。P3 进程应该先使用扫描仪,在使用打印机,但是实际上缺失先使用打印机,再使用扫描仪,导致打印机空闲了很长一段时间,等到扫描仪使用完了在使用打印机资源。因此导致系统资源的浪费

  • 必须按规定次序申请资源,用户编程麻烦。

2、避免死锁(银行家算法)

select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_05

2.1、什么是安全序列

你是一位成功的银行家,手里掌握着 select for update死锁springboot怎么解决 spooling解决死锁问题_银行家算法_06 个亿的资金…
有三个企业想找你贷款,分别是企业 B、企业 A、企业 T,为描述方便,简称 BAT

B 表示:“大哥,我最多会跟你借 select for update死锁springboot怎么解决 spooling解决死锁问题_互斥_07

A 表示:“大哥,我最多会跟你借 select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_08

T 表示:“大哥,我最多会跟你借 select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_09

然而…江湖中有个不成文的规矩:如果你借给企业的钱总数达不到企业提出的最大要求,那么不管你之前给企业借了多少钱,那些钱都拿不回来了…

刚开始,BAT 三个企业分别从你这儿借了 select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_10select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_11select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_12

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2JDPillw-1676912758828)(https://gitcode.net/qq_67720621/img/-/raw/master/typora-user-images-new/image-2023020521260485png)]

① 先给 Bselect for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_12

select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_14

所以给 Bselect for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_12


② 先给 Aselect for update死锁springboot怎么解决 spooling解决死锁问题_死锁_10 亿,再借给 B select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_09

select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_18


③ 先给 Aselect for update死锁springboot怎么解决 spooling解决死锁问题_死锁_10 亿,再借给 T select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_10

select for update死锁springboot怎么解决 spooling解决死锁问题_互斥_21


2.2、安全序列、不安全状态、死锁的联系

select for update死锁springboot怎么解决 spooling解决死锁问题_银行家算法_22

所谓安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个。

如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。

如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)


因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是 “银行家算法” 的核心思想。

2.3、银行家算法

银行家算法是荷兰学者 Dijkstra 为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。后来该算法被用在操作系统中,用于避免死锁。

核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态

  • 如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待。

select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_23


此时系统是否处于安全状态?

思路:尝试找出一个安全序列…{P1}

依次检查剩余可用资源 (3,3,2) 是否能满足各进程的需求

select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_24

① 可满足 P1 需求,将 P1 加入安全序列,并更新剩余可用资源值为(5,3,2)

依次检查剩余可用资源 (5,3,2) 是否能满足剩余进程(不包括已加入安全序列的进程)的需求

select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_25

说明如果优先把资源分配给 P3 ,那 P3 一定是可以顺利执行结束的。等 P3 结束了就会归还资源。

  • 于是,资源数就可以增加到 (2,1,1)+(5,3,2)=(7,4,3)

② 可满足 P3 需求,将 P3 加入安全序列,并更新剩余可用资源值为(7,4,3)

依次检查剩余可用资源 (7,4,3) 是否能满足剩余进程(不包括已加入安全序列的进程)的需求…

select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_26

……….

以此类推,共五次循环检查即可将 select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_27

该算法称为安全性算法。可以很方便地用代码实现以上流程,每一轮检查都从编号较小的进程开始检查实际做题时可以更快速的得到安全序列。


对于手算

① 可以找到安全序列的例子

select for update死锁springboot怎么解决 spooling解决死锁问题_互斥_28

实际做题(手算)时可用更快速的方法找到一个安全序列:
经对比发现,(3,3,2) 可满足 P1P3 ,说明无论如何,这两个进程的资源需求一定是可以依次被满足的,因此 P1P3 一定可以顺利的执行完,并归还资源。可把 P1P3 先加入安全序列。
(2,0,0)+(2,1,1)+(3,3,2)= (7,4,3)

select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_29

剩下的 P0P2P4 都可被满足。同理,这些进程都可以加入安全序列。

于是,select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_27

② 找不到安全序列的例子

select for update死锁springboot怎么解决 spooling解决死锁问题_银行家算法_31

经对比发现,(3,3,2) 可满足 P1P3,说明无论如何,这两个进程的资源需求一定是可以依次被满足的,因此P1P3 一定可以顺利的执行完,并归还资源。可把 P1P3 先加入安全序列。
(2,0,0)+(2,1,1)+(3,3,2)= (7,4,3)

select for update死锁springboot怎么解决 spooling解决死锁问题_银行家算法_32

剩下的 P0 需要 (8,4,3)P2 需要 (6,5,0)P4 需要 (4,3,4) 任何一个进程都不能被完全满足

于是,无法找到任何一个安全序列,说明此时系统处于不安全状态,有可能发生死锁。

2.4、Java 实现银行家算法

BankerAlgorithm.class

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class BankerAlgorithm {

    /**
     * @param maximumDemands 最大需求, 第 i 个进程的最大需求为数组 j
     * @param assigneds      已分配, 第 i 个进程的已分配的数量为数组 j
     * @param totalResource  可用资源数
     * @return 是否是安全序列
     */
    public boolean isSafeSequence(int[][] maximumDemands, int[][] assigneds, int[] totalResource) {
        //预处理, 获取当前进程最多还需要的需求
        int[][] mostNeededs = new int[maximumDemands.length][maximumDemands[0].length];
        int[] remainResource = totalResource;
        for (int i = 0; i < maximumDemands.length; i++) {
            //最多还需要
            int[] curNeed = new int[maximumDemands[i].length];
            //最大需求
            int[] curMaxDemand = maximumDemands[i];
            //已分配
            int[] curAssigned = assigneds[i];
            for (int j = 0; j < curMaxDemand.length; j++) {
                curNeed[j] = (curMaxDemand[j] - curAssigned[j]);
                int total = remainResource[j];
                remainResource[j] = total - curAssigned[j];
            }
            mostNeededs[i] = curNeed;
        }

        return dfs(assigneds, mostNeededs, remainResource, new boolean[maximumDemands.length], 0);
    }

    public boolean dfs(int[][] assigneds, int[][] mostNeededs, int[] remainResource, boolean[] isVisited, int cnt) {
        //被分配完了
        if (cnt == mostNeededs.length) {
            return true;
        }
        for (int i = 0; i < mostNeededs.length; i++) {
            //被访问过了
            if (isVisited[i]) {
                continue;
            }
            //满足需求
            int[] check = check(assigneds[i], mostNeededs[i], remainResource);
            if (check != null) {
                int[] temp = remainResource;
                remainResource = check;
                //移除当前进程
                isVisited[i] = true;
                if (dfs(assigneds, mostNeededs, remainResource, isVisited, cnt + 1)) {
                    return true;
                }
                isVisited[i] = false;
                remainResource = temp;
            }
        }
        return false;
    }

    //检查是否满足需求, 并且重新计算剩余资源数
    public int[] check(int[] assigned, int[] mostNeeded, int[] remainResource) {
        int[] ans = new int[mostNeeded.length];
        for (int i = 0; i < mostNeeded.length; i++) {
            int diff = remainResource[i] - mostNeeded[i];
            if (diff < 0) {
                return null;
            }
            ans[i] = assigned[i] + remainResource[i];
        }
        return ans;
    }
}

Test.class

public class Test{
    public static void main(String[] args) {
        BankerAlgorithm bankerAlgorithm = new BankerAlgorithm();
        //int[][] maximumDemands = new int[][]{{7, 5, 3}, {3, 2, 2}, {9, 0, 2}, {2, 2, 2}, {4, 3, 3}};
        //int[][] assigneds = new int[][]{{0, 1, 0}, {2, 0, 0}, {3, 0, 2}, {2, 1, 1}, {0, 0, 2}};
        //true

        int[][] maximumDemands = new int[][]{{8, 5, 3}, {3, 2, 2}, {9, 5, 2}, {2, 2, 2}, {4, 3, 6}};
        int[][] assigneds = new int[][]{{0, 1, 0}, {2, 0, 0}, {3, 0, 2}, {2, 1, 1}, {0, 0, 2}};
        //false
        
        int[] totalResource = new int[]{10, 5, 7};
        System.out.println(bankerAlgorithm.isSafeSequence(maximumDemands, assigneds, totalResource));
    }
}

2.5、总结

银行家算法步骤:

① 检查此次申请是否超过了之前声明的最大需求数

② 检查此时系统剩余的可用资源是否还能满足这次请求

③ 试探着分配,更改各数据结构

④ 用安全性算法检查此次分配是否会导致系统进入不安全状态

安全性算法步骤:

  • 检查当前的剩余可用资源是否能满足某个进程的最大需求,如果可以,就把该进程加入安全序列,并把该进程持有的资源全部回收。

系统处于不安全状态未必死锁,但死锁时一定处于不安全状态。系统处于安全状态一定不会死锁。

3、检测和解除

select for update死锁springboot怎么解决 spooling解决死锁问题_银行家算法_33

如果系统中既不采取预防死锁的措施,也不采取避免死锁的措施,系统就很可能发生死锁。在这种情况下,系统应当提供两个算法:

① 死锁检测算法:用于检测系统状态,以确定系统中是否发生了死锁。
② 死锁解除算法:当认定系统中已经发生了死锁,利用该算法可将系统从死锁状态中解脱出来。

3、死锁的检测

为了能对系统是否已发生了死锁进行检测,必须:

① 用某种数据结构来保存资源的请求和分配信息;

② 提供一种算法,利用上述信息来检测系统是否已进入死锁状态。

select for update死锁springboot怎么解决 spooling解决死锁问题_银行家算法_34

select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_35

3.1、Java 定义该数据结构(资源分配图)

① map 构造邻接表

public class ResourceAllocationGraph {
    /**
     * 对于 Map<Node, List<Pair>> map
     * 若 Node 为进程则 List<Pair> 为申请某些资源数量,
     * 例如:P1(false, 1) : [Pair(R1, 1)]
     * 表示 p1 请求资源的总数量为 1, 申请资源 R1 的数量为 1
     * 若 Node 为资源则 List<Pair> 为分配给进程的资源数量
     * 例如:R1(true, 0) : [Pair(P1, 2), Pair(P2, 1)]
     * 表示 R1 剩余资源为 0, 分配给 P1, p2 的资源数分别为 2, 1
     */
    List<Node> processes;
    List<Node> resources;
}

class Node {
    //是否是资源的标志, true : 资源, false : 进程
    boolean isResource;

    //若为资源: 当前剩余资源的总数量
    //若为进程: 当前进程请求资源的总数量
    int val;

    // 进程: pi 被分配的资源的节点 + 相应的分配数
    // 资源: ri 被请求的进程节点 + 相应的请求数
    Map<Node, Integer> inDegreeNodes;

    // 进程:pi 申请的资源节点 + 相应的申请数
    // 资源: ri 分配出去的进程节点 + 相应的分配数
    Map<Node, Integer> outDegreeNodes;
    public Node(boolean isResource, int val) {
        this.isResource = isResource;
        this.val = val;
    }
}

② 双向链表

public class ResourceAllocationGraph {
    Node prevProcess;
    Node nextProcess;
    Node prevResource;
    Node nextResource;
    int pSize();
    int rSize();
}
class Node {
    //是否是资源的标志, true : 资源, false : 进程
    boolean isResource;

    //若为资源: 当前剩余资源的总数量
    //若为进程: 当前进程请求资源的总数量
    int val;

    // 进程: pi 被分配的资源的节点 + 相应的分配数
    // 资源: ri 被请求的进程节点 + 相应的请求数
    Map<Node, Integer> inDegreeNodes;

    // 进程:pi 申请的资源节点 + 相应的申请数
    // 资源: ri 分配出去的进程节点 + 相应的分配数
    Map<Node, Integer> outDegreeNodes;
    public Node(boolean isResource, int val) {
        this.isResource = isResource;
        this.val = val;
    }
}

3.2、死锁检测的流程

① 如果系统中剩余的可用资源数足够满足进程的需求,那么这个进程暂时是不会阻塞的,可以顺利地执行下去。

② 如果这个进程执行结束了把资源归还系统,就可能使某些正在等待资源的进程被激活,并顺利地执行下去。

③ 相应的,这些被激活的进程执行完了之后又会归还一些资源,这样可能又会激活另外一些阻塞的进程…

如果按上述过程分析,最终能消除所有边,就称这个图是可完全简化的。此时一定没有发生死锁(相当于能找到一个安全序列)

如果最终不能消除所有边,那么此时就是发生了死锁

最终还连着边的那些进程就是处于死锁状态的进程。


安全序列的情况

select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_36

上述所示:

  • 对于 P2 来说,若 P2 想申请 R1 资源,但是 R1 已经分配出去 select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_37 个,因此 P2 的申请请求不能被满足
  • 对于 P1 来说,若 P1 想申请 R2资源,R2已经分给 P2 一个,还剩余 select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_38 个,所以 P1 可以得到满足。然后等待 P1 运行完毕之后,P1 会释放自己所拥有的资源(即:R1 :0 -> 2)。之后 P2也可以得到满足…
    具体流程如下所示:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gAA2x1Eo-1676912758833)(https://gitcode.net/qq_67720621/img/-/raw/master/typora-user-images-new/p.gif)]

产生死锁的情况

select for update死锁springboot怎么解决 spooling解决死锁问题_银行家算法_39

最终还连着边的那些进程就是处于死锁状态的进程

如下所示

select for update死锁springboot怎么解决 spooling解决死锁问题_互斥_40


3.3、死锁检测的算法

1)在资源分配图中,找出既不阻塞又不是孤点的进程 Pi(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量

  • 如下图中,R1 没有空闲资源,R2 有一个空闲资源。若所有的连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配变,使之称为孤立的结点。在下图中,P1 是满足这一条件的进程结点,于是将 P1 的所有边消去。

select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_41


2)进程 Pi 所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。

  • 在下图中,P2 就满足这样的条件。根据 1) 中的方法进行一系列简化后,若能消去途中所有的边,则称该图是可完全简化的。

P1 的所有边消去后的资源分配图

select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_42

P2 的所有边消去后的可完全简化的资源分配图

select for update死锁springboot怎么解决 spooling解决死锁问题_死锁_43


3.4、 Java 实现死锁检测的算法

资源分配图

//资源分配图
class ResourceAllocationGraph {
    /**
     * 对于 Map<Node, List<Pair>> map
     * 若 Node 为进程则 List<Pair> 为申请某些资源数量,
     * 例如:P1(false, 1) : [Pair(R1, 1)]
     * 表示 p1 请求资源的总数量为 1, 申请资源 R1 的数量为 1
     * 若 Node 为资源则 List<Pair> 为分配给进程的资源数量
     * 例如:R1(true, 0) : [Pair(P1, 2), Pair(P2, 1)]
     * 表示 R1 剩余资源为 0, 分配给 P1, p2 的资源数分别为 2, 1
     */
    List<Node> processes;
    List<Node> resources;
}

class Node {
    //是否是资源的标志, true : 资源, false : 进程
    boolean isResource;

    //若为资源: 当前剩余资源的总数量
    //若为进程: 当前进程请求资源的总数量
    int val;

    // 进程: pi 被分配的资源的节点 + 相应的分配数
    // 资源: ri 被请求的进程节点 + 相应的请求数
    Map<Node, Integer> inDegreeNodes;

    // 进程:pi 申请的资源节点 + 相应的申请数
    // 资源: ri 分配出去的进程节点 + 相应的分配数
    Map<Node, Integer> outDegreeNodes;
    public Node(boolean isResource, int val) {
        this.isResource = isResource;
        this.val = val;
    }
}

step 1 : 构建资源分配图

select for update死锁springboot怎么解决 spooling解决死锁问题_开发语言_44

public ResourceAllocationGraph builderResourceGraph() {
    // 资源
    Node r1 = new Node(true, 0);
    Node r2 = new Node(true, 1);
    // 进程
    //        Node p1 = new Node(false, 1);
    Node p1 = new Node(false, 2);
    Node p2 = new Node(false, 1);

    // r1 分配出去的资源和请求 r1 资源的进程
    r1.outDegreeNodes = new HashMap<>();
    r1.outDegreeNodes.put(p1, 2);
    r1.inDegreeNodes = new HashMap<>();
    r1.inDegreeNodes.put(p2, 1);
    // r2 分配出去的资源和请求 r1 资源的进程
    r2.outDegreeNodes = new HashMap<>();
    r2.outDegreeNodes.put(p2, 1);
    r2.inDegreeNodes = new HashMap<>();
    //        r2.inDegreeNodes.put(p1, 1);
    r2.inDegreeNodes.put(p1, 2);
    // p1 申请的资源和被分配的资源
    p1.outDegreeNodes = new HashMap<>();
    //        p1.outDegreeNodes.put(r2, 1);
    p1.outDegreeNodes.put(r2, 2);
    p1.inDegreeNodes = new HashMap<>();
    p1.inDegreeNodes.put(r1, 2);
    // p2 申请的资源和被分配的资源
    p2.outDegreeNodes = new HashMap<>();
    p2.outDegreeNodes.put(r1, 1);
    p2.inDegreeNodes = new HashMap<>();
    p2.inDegreeNodes.put(r2, 1);
    // 建立资源分配图
    ResourceAllocationGraph graph = new ResourceAllocationGraph();
    graph.processes = new ArrayList<>();
    graph.processes.add(p1);
    graph.processes.add(p2);
    graph.resources = new ArrayList<>();
    graph.resources.add(r1);
    graph.resources.add(r2);
    return graph;
}

step 2 : 死锁检测, true : 死锁, false : 不产生死锁

public boolean hasDeadlock(ResourceAllocationGraph graph, boolean[] isolated, int cnt) {
    //1. 找出即不阻塞又不是孤立的进程 pi
    //  即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量
    List<Node> processes = graph.processes;
    // 所有的 pi 都孤立了
    if (cnt == 0) {
        return false;
    }
    //可能有多个
    List<Node> pis = new ArrayList<>();
    // pi 申请的资源必须全部满足条件
    for (int i = 0; i < processes.size(); i++) {
        //说明 pi 已经被孤立了
        if (isolated[i]) {
            continue;
        }
        Node pi = processes.get(i);
        // pi 申请的资源节点剩余资源 >= pi 申请的资源
        boolean flag = true;
        for (Map.Entry<Node, Integer> entry : pi.outDegreeNodes.entrySet()) {
            Node node = entry.getKey();
            if (node.val < entry.getValue()) {
                flag = false;
                break;
            }
        }
        if (flag) {
            pis.add(pi);
        }
    }
    //没有 pi ,产生了死锁
    if (pis.size() == 0) {
        return true;
    }
    //2. pi 释放资源
    for (int i = 0; i < pis.size(); i++) {
        Node pi = pis.get(i);
        //pi 对应申请的相应资源节点, 释放 pi
        pi.outDegreeNodes.forEach((node, integer) -> {
            node.inDegreeNodes.remove(pi);
        });
        //分配给 pi 的相应资源节点 增加相应的资源数, 并释放 pi
        pi.inDegreeNodes.forEach((node, integer) -> {
            node.val += integer;
            node.outDegreeNodes.remove(pi);
        });
        //让 pi 孤立
        isolated[i] = true;
        cnt--;
    }
    // 递归地检测, 没有死锁 false, 有死锁 true
    return hasDeadlock(graph, isolated, cnt);
}

整体代码

public class DeadlockDetection {
    public static void main(String[] args) {
        //1. 构建资源分配图
        DeadlockDetection deadlockDetection = new DeadlockDetection();
        ResourceAllocationGraph graph = deadlockDetection.builderResourceGraph();
        //2. 死锁检测, true : 死锁, false : 不产生死锁
        System.out.println(deadlockDetection.hasDeadlock(graph, new boolean[graph.processes.size()], graph.processes.size()));
    }

    public boolean hasDeadlock(ResourceAllocationGraph graph, boolean[] isolated, int cnt) {
        //1. 找出即不阻塞又不是孤立的进程 pi
        //  即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量
        List<Node> processes = graph.processes;
        // 所有的 pi 都孤立了
        if (cnt == 0) {
            return false;
        }
        //可能有多个
        List<Node> pis = new ArrayList<>();
        // pi 申请的资源必须全部满足条件
        for (int i = 0; i < processes.size(); i++) {
            //说明 pi 已经被孤立了
            if (isolated[i]) {
                continue;
            }
            Node pi = processes.get(i);
            // pi 申请的资源节点剩余资源 >= pi 申请的资源
            boolean flag = true;
            for (Map.Entry<Node, Integer> entry : pi.outDegreeNodes.entrySet()) {
                Node node = entry.getKey();
                if (node.val < entry.getValue()) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                pis.add(pi);
            }
        }
        //没有 pi ,产生了死锁
        if (pis.size() == 0) {
            return true;
        }
        //2. pi 释放资源
        for (int i = 0; i < pis.size(); i++) {
            Node pi = pis.get(i);
            //pi 对应申请的相应资源节点, 释放 pi
            pi.outDegreeNodes.forEach((node, integer) -> {
                node.inDegreeNodes.remove(pi);
            });
            //分配给 pi 的相应资源节点 增加相应的资源数, 并释放 pi
            pi.inDegreeNodes.forEach((node, integer) -> {
                node.val += integer;
                node.outDegreeNodes.remove(pi);
            });
            //让 pi 孤立
            isolated[i] = true;
            cnt--;
        }
        // 递归地检测, 没有死锁 false, 有死锁 true
        return hasDeadlock(graph, isolated, cnt);
    }

    public ResourceAllocationGraph builderResourceGraph() {
        // 资源
        Node r1 = new Node(true, 0);
        Node r2 = new Node(true, 1);
        // 进程
//        Node p1 = new Node(false, 1);
        Node p1 = new Node(false, 2);
        Node p2 = new Node(false, 1);

        // r1 分配出去的资源和请求 r1 资源的进程
        r1.outDegreeNodes = new HashMap<>();
        r1.outDegreeNodes.put(p1, 2);
        r1.inDegreeNodes = new HashMap<>();
        r1.inDegreeNodes.put(p2, 1);
        // r2 分配出去的资源和请求 r1 资源的进程
        r2.outDegreeNodes = new HashMap<>();
        r2.outDegreeNodes.put(p2, 1);
        r2.inDegreeNodes = new HashMap<>();
//        r2.inDegreeNodes.put(p1, 1);
        r2.inDegreeNodes.put(p1, 2);
        // p1 申请的资源和被分配的资源
        p1.outDegreeNodes = new HashMap<>();
//        p1.outDegreeNodes.put(r2, 1);
        p1.outDegreeNodes.put(r2, 2);
        p1.inDegreeNodes = new HashMap<>();
        p1.inDegreeNodes.put(r1, 2);
        // p2 申请的资源和被分配的资源
        p2.outDegreeNodes = new HashMap<>();
        p2.outDegreeNodes.put(r1, 1);
        p2.inDegreeNodes = new HashMap<>();
        p2.inDegreeNodes.put(r2, 1);
        // 建立资源分配图
        ResourceAllocationGraph graph = new ResourceAllocationGraph();
        graph.processes = new ArrayList<>();
        graph.processes.add(p1);
        graph.processes.add(p2);
        graph.resources = new ArrayList<>();
        graph.resources.add(r1);
        graph.resources.add(r2);
        return graph;
    }
}

//资源分配图
class ResourceAllocationGraph {
    /**
     * 对于 Map<Node, List<Pair>> map
     * 若 Node 为进程则 List<Pair> 为申请某些资源数量,
     * 例如:P1(false, 1) : [Pair(R1, 1)]
     * 表示 p1 请求资源的总数量为 1, 申请资源 R1 的数量为 1
     * 若 Node 为资源则 List<Pair> 为分配给进程的资源数量
     * 例如:R1(true, 0) : [Pair(P1, 2), Pair(P2, 1)]
     * 表示 R1 剩余资源为 0, 分配给 P1, p2 的资源数分别为 2, 1
     */
    List<Node> processes;
    List<Node> resources;
}

class Node {
    //是否是资源的标志, true : 资源, false : 进程
    boolean isResource;

    //若为资源: 当前剩余资源的总数量
    //若为进程: 当前进程请求资源的总数量
    int val;

    // 进程: pi 被分配的资源的节点 + 相应的分配数
    // 资源: ri 被请求的进程节点 + 相应的请求数
    Map<Node, Integer> inDegreeNodes;

    // 进程:pi 申请的资源节点 + 相应的申请数
    // 资源: ri 分配出去的进程节点 + 相应的分配数
    Map<Node, Integer> outDegreeNodes;
    public Node(boolean isResource, int val) {
        this.isResource = isResource;
        this.val = val;
    }
}

3.5、死锁定理

死锁定理:如果某时刻系统的资源分配图是不可完全简化的,那么此时系统死锁


4、死锁的解除

一旦检测出死锁的发生,就应该立即解除死锁。

补充:并不是系统中所有的进程都是死锁状态,用死锁检测算法化简资源分配图后,还连着边的那些进程就是死锁进程


4.1、资源剥夺法

资源剥夺法:

  • 挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。
  • 但是应防止被挂起的进程长时间得不到资源而饥饿

4.2、撤销进程法(终止进程法)

撤销进程法(或称终止进程法):

  • 强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。

这种方式的优点是实现简单,但所付出的代价可能会很大。

  • 因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止可谓功亏一篑,以后还得从头再来。

4.3、进程回退法

进程回退法:

  • 让一个或多个死锁进程回退到足以避免死锁的地步。
  • 这就要求系统要记录进程的历史信息,设置还原点

4.4、如何决定 “对谁动手”

① 进程优先级

  • 例如:进程优先级低的对其下手

② 已执行多长时间

  • 例如:执行时间更少的进程,让其牺牲

③ 还要多久能完成

  • 例如:牺牲掉还需要更长的时间完成,优先让即将完成的进程获得资源

④ 进程已经使用了多少资源

  • 例如:优先牺牲掉已经使用更多资源的进程

⑤ 进程是交互式的还是批处理式的

  • 若牺牲掉交互式的(即:正在与用户交互的进程),则用户不满意
  • 批处理式的无非就是在做一些计算,对用户的及时反馈并不那么在意
  • 所以优先牺牲掉批处理式的

5、整体框架

select for update死锁springboot怎么解决 spooling解决死锁问题_互斥_45