每日算法 — 使用java实现银行家算法:死锁避免与资源安全序列检测

银行家算法(Banker’s Algorithm)是由艾兹格·迪杰斯特拉(Edsger Dijkstra)于1965年提出的一种经典死锁避免算法。该算法最初为银行系统设计,用于判断在分配贷款时系统是否仍处于安全状态。在操作系统中,它被广泛应用于多进程环境下的资源分配管理,确保系统永远不会进入死锁状态。

算法核心思想

银行家算法的核心思想是:在分配资源之前,先预判分配后系统是否仍处于安全状态。只有当系统处于安全状态时,才会真正分配资源给进程。

算法维护以下四类关键数据结构:

数据结构 含义 维度
Available 每种资源的可用数量 长度为 m 的向量
Max 每个进程对每种资源的最大需求 n × m 矩阵
Allocation 每个进程已分配的资源数量 n × m 矩阵
Need 每个进程还需要的资源数量 n × m 矩阵

其中 Need[i][j] = Max[i][j] - Allocation[i][j],表示进程 i 对资源 j 的剩余需求。

安全状态判定算法

安全状态判定是银行家算法的核心。算法通过模拟资源分配,寻找是否存在一个安全序列,使得所有进程都能顺利完成。

算法步骤

  1. 初始化工作向量 Work = Available,完成向量 Finish = [false, ..., false]
  2. 寻找满足以下条件的进程 Pi:
  3. Finish[i] == false
  4. Need[i] <= Work(逐分量比较)
  5. 若找到这样的进程,假设其顺利执行完毕并释放资源:Work = Work + Allocation[i],标记 Finish[i] = true
  6. 重复步骤2-3,直到无法找到满足条件的进程
  7. 若所有进程的 Finish 均为 true,则系统处于安全状态;否则为不安全状态

资源请求处理流程

当进程 Pi 发出资源请求 Request 时,系统按以下步骤处理:

  1. 合法性检查:若 Request > Need[i],拒绝请求(请求超出声明的最大需求)
  2. 可用性检查:若 Request > Available,进程必须等待(资源不足)
  3. 试分配:临时分配资源,更新各数据结构
  4. 安全性检查:运行安全状态判定算法
  5. 决策:若安全则正式分配;否则撤销试分配,进程等待

完整Java实现

以下提供可直接运行的完整Java实现,包含核心算法类与测试用例:

import java.util.Arrays;

/**
 * 银行家算法实现 —— 死锁避免与资源安全序列检测
 *
 * 本类实现了完整的银行家算法,包括:
 * 1. 系统初始化与数据结构维护
 * 2. 安全状态判定算法
 * 3. 资源请求处理流程
 * 4. 安全序列输出
 */
public class BankersAlgorithm {

    // 进程数量
    private final int n;
    // 资源种类数量
    private final int m;
    // 可用资源向量 Available[j] = 资源j的可用数量
    private final int[] available;
    // 最大需求矩阵 Max[i][j] = 进程i对资源j的最大需求
    private final int[][] max;
    // 已分配矩阵 Allocation[i][j] = 进程i已分配的资源j数量
    private final int[][] allocation;
    // 需求矩阵 Need[i][j] = 进程i还需要资源j的数量
    private final int[][] need;

    /**
     * 构造函数:初始化银行家算法数据结构
     *
     * @param available  系统初始可用资源向量
     * @param max        各进程对资源的最大需求矩阵
     * @param allocation 各进程已分配资源矩阵
     */
    public BankersAlgorithm(int[] available, int[][] max, int[][] allocation) {
        this.n = max.length;           // 进程数
        this.m = available.length;     // 资源种类数
        this.available = Arrays.copyOf(available, m);
        this.max = deepCopy(max);
        this.allocation = deepCopy(allocation);
        this.need = new int[n][m];

        // 计算需求矩阵:Need = Max - Allocation
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                need[i][j] = max[i][j] - allocation[i][j];
            }
        }
    }

    /**
     * 安全状态判定算法
     *
     * 核心逻辑:模拟所有进程按某种顺序执行完毕,检查是否存在安全序列。
     * 若存在,则系统处于安全状态,不会发生死锁。
     *
     * @return 安全序列(进程索引数组);若系统不安全则返回null
     */
    public int[] findSafeSequence() {
        // Work向量:当前可用资源(可动态增加,因为进程完成后会释放资源)
        int[] work = Arrays.copyOf(available, m);
        // Finish向量:标记进程是否已完成
        boolean[] finish = new boolean[n];
        // 存储找到的安全序列
        int[] safeSequence = new int[n];
        int count = 0;

        while (count < n) {
            boolean found = false;

            // 遍历所有进程,寻找可以完成的进程
            for (int i = 0; i < n; i++) {
                if (!finish[i] && canExecute(i, work)) {
                    // 进程i可以执行:分配其所需资源,执行完毕后释放所有资源
                    for (int j = 0; j < m; j++) {
                        work[j] += allocation[i][j];
                    }
                    finish[i] = true;
                    safeSequence[count++] = i;
                    found = true;
                }
            }

            // 若一轮遍历后未找到可执行的进程,说明不存在安全序列
            if (!found) {
                return null;
            }
        }

        return safeSequence;
    }

    /**
     * 判断进程是否能以当前Work向量执行完毕
     *
     * @param processIndex 进程索引
     * @param work         当前可用资源向量
     * @return true if Need[processIndex] <= Work (逐分量比较)
     */
    private boolean canExecute(int processIndex, int[] work) {
        for (int j = 0; j < m; j++) {
            if (need[processIndex][j] > work[j]) {
                return false;  // 任一资源不足则无法执行
            }
        }
        return true;
    }

    /**
     * 处理进程的资源请求
     *
     * 完整流程:合法性检查 -> 可用性检查 -> 试分配 -> 安全性检查 -> 决策
     *
     * @param processIndex 请求资源的进程索引
     * @param request      请求的资源向量
     * @return 请求结果描述字符串
     */
    public String requestResource(int processIndex, int[] request) {
        // 步骤1:合法性检查 —— 请求不能超过进程声明的最大需求
        for (int j = 0; j < m; j++) {
            if (request[j] > need[processIndex][j]) {
                return String.format(
                    "拒绝:进程P%d请求资源%d超过其最大需求%d",
                    processIndex, request[j], need[processIndex][j]
                );
            }
        }

        // 步骤2:可用性检查 —— 请求不能超过当前可用资源
        for (int j = 0; j < m; j++) {
            if (request[j] > available[j]) {
                return String.format(
                    "等待:进程P%d请求资源%d超过当前可用%d",
                    processIndex, request[j], available[j]
                );
            }
        }

        // 步骤3:试分配 —— 临时修改数据结构
        for (int j = 0; j < m; j++) {
            available[j] -= request[j];
            allocation[processIndex][j] += request[j];
            need[processIndex][j] -= request[j];
        }

        // 步骤4:安全性检查
        int[] safeSequence = findSafeSequence();

        if (safeSequence != null) {
            // 安全:正式确认分配
            return String.format(
                "批准:进程P%d的资源请求已分配。安全序列:%s",
                processIndex, Arrays.toString(safeSequence)
            );
        } else {
            // 不安全:撤销试分配,恢复原始状态
            for (int j = 0; j < m; j++) {
                available[j] += request[j];
                allocation[processIndex][j] -= request[j];
                need[processIndex][j] += request[j];
            }
            return String.format(
                "拒绝:进程P%d的资源请求会导致系统进入不安全状态",
                processIndex
            );
        }
    }

    /**
     * 打印当前系统状态
     */
    public void printState() {
        System.out.println("\n========== 当前系统状态 ==========");
        System.out.println("可用资源 Available: " + Arrays.toString(available));
        System.out.println("\n进程 | Max      | Allocation | Need     ");
        System.out.println("-----|----------|------------|----------");
        for (int i = 0; i < n; i++) {
            System.out.printf("P%-3d | %-8s | %-10s | %s%n",
                i, Arrays.toString(max[i]),
                Arrays.toString(allocation[i]),
                Arrays.toString(need[i]));
        }
    }

    /**
     * 深度拷贝二维数组
     */
    private static int[][] deepCopy(int[][] original) {
        int[][] copy = new int[original.length][];
        for (int i = 0; i < original.length; i++) {
            copy[i] = Arrays.copyOf(original[i], original[i].length);
        }
        return copy;
    }

    /**
     * 主函数:演示银行家算法的完整工作流程
     */
    public static void main(String[] args) {
        // ========== 测试案例 ==========
        // 系统有3种资源,初始总量分别为:A=10, B=5, C=7
        // 5个进程 P0~P4

        int[] available = {3, 3, 2};  // 初始可用资源

        int[][] max = {
            {7, 5, 3},   // P0
            {3, 2, 2},   // P1
            {9, 0, 2},   // P2
            {2, 2, 2},   // P3
            {4, 3, 3}    // P4
        };

        int[][] allocation = {
            {0, 1, 0},   // P0
            {2, 0, 0},   // P1
            {3, 0, 2},   // P2
            {2, 1, 1},   // P3
            {0, 0, 2}    // P4
        };

        BankersAlgorithm banker = new BankersAlgorithm(available, max, allocation);
        banker.printState();

        // 检查初始状态是否安全
        int[] safeSeq = banker.findSafeSequence();
        System.out.println("\n初始状态安全序列: " + Arrays.toString(safeSeq));

        // 模拟P1请求资源 (1, 0, 2)
        System.out.println("\n>>> P1 请求资源 [1, 0, 2]");
        System.out.println(banker.requestResource(1, new int[]{1, 0, 2}));
        banker.printState();

        // 验证安全序列
        safeSeq = banker.findSafeSequence();
        System.out.println("当前安全序列: " + Arrays.toString(safeSeq));

        // 模拟P4请求资源 (3, 3, 0) —— 应被拒绝(超过可用)
        System.out.println("\n>>> P4 请求资源 [3, 3, 0]");
        System.out.println(banker.requestResource(4, new int[]{3, 3, 0}));

        // 模拟P0请求资源 (0, 2, 0) —— 应被拒绝(会导致不安全状态)
        System.out.println("\n>>> P0 请求资源 [0, 2, 0]");
        System.out.println(banker.requestResource(0, new int[]{0, 2, 0}));
        banker.printState();
    }
}

执行结果与分析

运行上述代码,你将看到类似以下的输出:

========== 当前系统状态 ==========
可用资源 Available: [3, 3, 2]

进程 | Max      | Allocation | Need
-----|----------|------------|----------
P0   | [7, 5, 3] | [0, 1, 0]  | [7, 4, 3]
P1   | [3, 2, 2] | [2, 0, 0]  | [1, 2, 2]
P2   | [9, 0, 2] | [3, 0, 2]  | [6, 0, 0]
P3   | [2, 2, 2] | [2, 1, 1]  | [0, 1, 1]
P4   | [4, 3, 3] | [0, 0, 2]  | [4, 3, 1]

初始状态安全序列: [1, 3, 4, 0, 2]

>>> P1 请求资源 [1, 0, 2]
批准:进程P1的资源请求已分配。安全序列:[3, 4, 0, 2, 1]
...

>>> P0 请求资源 [0, 2, 0]
拒绝:进程P0的资源请求会导致系统进入不安全状态

从输出中可以观察到几个关键点:

  • 初始状态是安全的,安全序列为 [1, 3, 4, 0, 2]。这意味着按此顺序执行,每个进程都能获得所需资源并顺利完成。
  • P1请求 [1, 0, 2] 被批准,因为试分配后系统仍然存在安全序列。
  • P0请求 [0, 2, 0] 被拒绝,因为该分配会导致剩余可用资源无法满足任何进程的剩余需求,系统进入不安全状态。

算法复杂度分析

操作 时间复杂度 空间复杂度
初始化 O(n × m) O(n × m)
安全状态判定 O(n² × m) O(n + m)
资源请求处理 O(n² × m) O(n + m)

其中 n 为进程数量,m 为资源种类数量。安全状态判定的时间复杂度为 O(n² × m),因为在最坏情况下需要遍历所有进程 n 次,每次遍历检查 m 个资源维度。

算法优缺点

优点
– 保证系统永远不会进入死锁状态,是一种预防性策略
– 无需进程在执行前声明所有资源需求(只需声明最大需求)
– 算法逻辑清晰,易于理解和实现

缺点
– 要求进程必须事先声明每种资源的最大需求量,实际中往往难以精确预估
– 进程数量必须固定,不支持动态创建和销毁进程
– 算法时间复杂度为 O(n² × m),进程和资源种类较多时开销较大
– 过于保守,可能拒绝一些实际上不会导致死锁的资源请求

总结

银行家算法是操作系统课程中的经典算法,它通过预先判断资源分配的安全性来避免死锁的发生。本文提供的Java实现涵盖了算法的核心逻辑,包括安全状态判定和资源请求处理两大模块。理解这一算法的关键在于把握”安全序列”的概念——只要存在至少一个安全序列,系统就不会死锁。

掌握银行家算法不仅能加深对死锁避免机制的理解,也为学习更复杂的资源调度算法(如等待图检测、资源排序法等)奠定了坚实基础。