銀行家算法-解析與編碼(Java實現)

銀行家算法

Banker’s Algorithm是一個避免Deadlock的算法,是由艾茲格·迪傑斯特拉在1965年爲T.H.E系統設計。

算法背景

在銀行中,客戶申請貸款的數量是有限的,每個客戶在第一次申請貸款時要聲明完成該項目所需的最大資金量,在滿足所有貸款要求時,客戶應及時歸還
銀行家在客戶申請的貸款數量不超過之前聲明的最大值時,都應儘量滿足更多客戶的需要。

可以看到關鍵字:

  • 資金不能無限借需要聲明最大資金量。
  • 客戶借滿了以後,需要及時歸還(通常這點不太容易做到…)。
  • 算法的目的是儘量滿足跟多客戶的需求(沒有側重,沒有VIP,通常來說這點也不太容易做到,畢竟商業追求利益最大化…)。

當然,我們不去考慮太多其他策略,單就算法而論。

類比操作系統:

  • 銀行的資金相當於OS中的資源(CPU,內存,線程etc.);
  • 銀行的用戶相當於OS中的進程;

由於該算法在避免死鎖的方法中,所施加的限制條件較弱,有可能(敲黑板!)獲得令人滿意的系統性能。

算法內容

基本思想:分配資源之前,先判斷分配後系統是否是安全的;若是,才分配;否則,回滾分配;
由此可見,算法的核心應該是判斷系統是否安全。

先看百科對系統安全的定義:安全序列是指一個進程序列{P1,…,Pn}是安全的,即對於每一個進程Pi(1≤i≤n),它以後尚需要的資源量不超過系統當前剩餘資源量與所有進程Pj (j < i )當前佔有資源量之和。(反正我是沒看明白…)

所謂安全通俗來說就是:分給進程A以後,我要先看一下:現有的空閒資源加上其他進程歸還的資源後,不會讓任何一個進程永遠處於等待狀態。

下面看一下整個流程:

數據結構

  • 數組int[] mAvailable:表示系統當前空閒資源;
  • 二維數組int[][] mAllocated:各進程當前已經持有的資源數;
  • 二維數組int[][] mNeed:各進程還需要的資源數;

算法主流程

  1. 如果進程i請求資源j大於進程i還需要的資源數mNeed[i][j],則出錯;否則繼續下一步。
  2. 如果進程i請求資源j大於當前該資源的空閒數mAvailable[i][j],則申請失敗進程等待;否則繼續下一步。
  3. 先執行分配,再檢查系統安全性,若安全則分配生效;否則,回滾分配。

檢查系統安全性

  1. 先檢查當前觸發安全性檢查的進程i是否已經完成分配,若已完成則假設其迅速歸還;否則不做處理繼續下一步。
  2. 從所有進程中找到未完成且當前資源可以爲其完成分配的進程,再假設已完成分配且其歸還所有資源;重複此步驟,直到再也找不到滿足條件的進程。
  3. 檢查所有進程的狀態,如果存在未標記Over的進程,則系統不安全,否則系統安全。

注:
上面說的完成分配,理解成分配給進程其初始宣稱的所有資源的最大數量。

Talk is Cheap,Show you the Code:

申請資源部分

/**
* 進程申請資源
*
* @param i 進程編號
* @param j 資源編號
* @param x 資源數量
* @return 0:申請成功,-1:錯誤,1:資源不足,2:不安全分配
*/
public int apply(int i, int j, int x) {
    if (x > mNeed[i][j]) {
        Main.d(i + "進程請求資源數" + j + "(數量:" + x + "),大於初始宣稱數" + mNeed[i][j] + ",拒絕分配!");
        return -1;
    }
    // 如果請求數量超過了當前系統空閒資源,需要先等待
    if (x > mAvailable[j]) {
        Main.d(i + "進程請求資源數" + j + "(數量:" + x + "),大於資源空閒數" + mAvailable[j] + ",需要等待");
        return 1;
    }
    // 否則,先分配給進程
    mAvailable[j] -= x;
    mAllocated[i][j] += x;
    mNeed[i][j] -= x;
    // 分配後測試系統安全性
    if (!checkSafe(i)) {
        // 如果系統不安全,則撤回分配
        mAvailable[j] += x;
        mAllocated[i][j] -= x;
        mNeed[i][j] += x;
        Main.d(i + "進程請求資源" + j + "(數量:" + x + "),分配後導致系統不安全,需要等待...");
        return 2;
    }
    Main.d(i + "進程請求資源" + j + "(數量:" + x + "),分配成功!");
    // 分配後如果系統安全,則分配成立
    return 0;
}

安全性檢查部分

/**
* 測試系統是否安全
*
* @param k 觸發檢查的進程編號
* @return 系統在分配k進程資源後是否處於安全狀態
*/
public boolean checkSafe(int k) {
    int[] work = new int[mAvailable.length];
    System.arraycopy(mAvailable, 0, work, 0, mAvailable.length);
    // 當前觸發檢查的進程是k,需要看一下進程在此次分配後是否可以結束,如果可以,需要退還名下資源
    returnResIfFinished(work, k);

    // 初始化各進程的完成狀態
    boolean[] finish = new boolean[mAllocated.length];
    boolean isOk;
    for (int i = 0; i < mAllocated.length; i++) {
        isOk = true;
        for (int j = 0; j < mAvailable.length; j++) {
            if (mNeed[i][j] > 0) {
                isOk = false;
                break;
            }
        }
        finish[i] = isOk;
    }

    for (int i = 0; i < finish.length; i++) {
        if (finish[i]) {
            continue;
        }
        // 在未完成的進程中,找到一個可以分配資源的進程
        isOk = true;
        for (int j = 0; j < work.length; j++) {
            if (mNeed[i][j] > work[j]) {
                isOk = false;
                break;
            }
        }
        if (!isOk) {
            continue;
        }
        // 如果可以,就假設把資源分配給了它,且假設它運行完成了並退還了資源
        finish[i] = true;
        for (int j = 0; j < work.length; j++) {
            work[j] += mAllocated[i][j];
        }
        // 類似topo排序,空閒資源有退還,重新掃描
        i = 0;
    }
    // 最後檢查,是否所有的進程最後都可以安全結束
    for (boolean ans : finish) {
        if (!ans) {
            return false;
        }
    }
    return true;
}

最後,附上測試DEMO:
筆者注:除去註釋後核心類SystemInfo代碼約100行,相對來說理解起來不算太困難。

完整DEMO

public class Main {

    public static void main(String[] args) throws Exception {
        SystemInfo system = new SystemInfo(3, 1, 2);
        system.init(new int[][]{{2, 1, 0}, {3, 0, 2}, {1, 1, 1}, {2, 1, 2}, {3, 1, 0}, {3, 1, 2}});

        request(system, 0, 0, 2);
        request(system, 0, 1, 1);

        request(system, 1, 0, 1);
        request(system, 1, 0, 2);
        request(system, 1, 2, 1);
        request(system, 1, 2, 1);

        request(system, 2, 0, 1);
        request(system, 2, 1, 1);
        request(system, 2, 2, 1);

        request(system, 3, 0, 1);
        request(system, 3, 0, 1);
        request(system, 3, 1, 1);
        request(system, 3, 2, 2);

        request(system, 4, 0, 1);
        request(system, 4, 0, 2);
        request(system, 4, 1, 1);

        request(system, 5, 0, 3);
        request(system, 5, 1, 1);
        request(system, 5, 2, 1);
        request(system, 5, 2, 1);

    }

    private static void request(SystemInfo sys, int i, int j, int x) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 申請系統資源,如果失敗就等待300~600ms後重試
                while (0 != sys.apply(i, j, x)) {
                    try {
                        Thread.sleep((long) (Math.random() * 300 + 300));
                    } catch (InterruptedException ignore) {
                    }
                }
                // 模擬資源使用 500~1000 ms
                try {
                    Thread.sleep((long) (Math.random() * 500 + 500));
                } catch (InterruptedException ignore) {
                }
                sys.returnResIfFinished(i);
            }
        }).start();
    }

    public static void d(Object info) {
        System.out.println(info);
    }
}

class SystemInfo {
    // 系統可用資源
    private int[] mAvailable;

    // 各進程當前已經持有的資源數
    private int[][] mAllocated;
    // 各進程還需要的資源數
    private int[][] mNeed;

    /**
     * 初始化
     *
     * @param res 系統資源
     */
    public SystemInfo(int... res) {
        mAvailable = new int[res.length];
        System.arraycopy(res, 0, mAvailable, 0, res.length);
    }

    /**
     * 初始化,各進程宣稱對各種資源的最大需求
     *
     * @param max 最大需求量
     */
    public void init(int[][] max) {
        assert null != max;
        assert max.length > 0;
        assert max[0].length == mAvailable.length;

        mAllocated = new int[max.length][mAvailable.length];
        mNeed = new int[max.length][mAvailable.length];

        for (int i = 0; i < max.length; i++) {
            System.arraycopy(max[i], 0, mNeed[i], 0, mAvailable.length);
            // TODO 這個時候應該評估一下,把所有資源都給某一個進程,是否能滿足需求,若不能則申請失敗
        }
    }

    /**
     * 進程申請資源
     *
     * @param i 進程編號
     * @param j 資源編號
     * @param x 資源數量
     * @return 0:申請成功,-1:錯誤,1:資源不足,2:不安全分配
     */
    public synchronized int apply(int i, int j, int x) {
        if (x > mNeed[i][j]) {
            Main.d(i + "進程請求資源數" + j + "(數量:" + x + "),大於初始宣稱數" + mNeed[i][j] + ",拒絕分配!");
            return -1;
        }
        // 如果請求數量超過了當前系統空閒資源,需要先等待
        if (x > mAvailable[j]) {
            Main.d(i + "進程請求資源數" + j + "(數量:" + x + "),大於資源空閒數" + mAvailable[j] + ",需要等待");
            return 1;
        }
        // 否則,先分配給進程
        mAvailable[j] -= x;
        mAllocated[i][j] += x;
        mNeed[i][j] -= x;
        // 分配後測試系統安全性
        if (!checkSafe(i)) {
            // 如果系統不安全,則撤回分配
            mAvailable[j] += x;
            mAllocated[i][j] -= x;
            mNeed[i][j] += x;
            Main.d(i + "進程請求資源" + j + "(數量:" + x + "),分配後導致系統不安全,需要等待...");
            return 2;
        }
        Main.d(i + "進程請求資源" + j + "(數量:" + x + "),分配成功!");
        // 分配後如果系統安全,則分配成立
        return 0;
    }

    /**
     * 測試系統是否安全
     *
     * @param k 觸發檢查的進程編號
     * @return 系統在分配k進程資源後是否處於安全狀態
     */
    public boolean checkSafe(int k) {
        int[] work = new int[mAvailable.length];
        System.arraycopy(mAvailable, 0, work, 0, mAvailable.length);
        // 當前觸發檢查的進程是k,需要看一下進程在此次分配後是否可以結束,如果可以,需要退還名下資源
        returnResIfFinished(work, k);

        // 初始化各進程的完成狀態
        boolean[] finish = new boolean[mAllocated.length];
        boolean isOk;
        for (int i = 0; i < mAllocated.length; i++) {
            isOk = true;
            for (int j = 0; j < mAvailable.length; j++) {
                if (mNeed[i][j] > 0) {
                    isOk = false;
                    break;
                }
            }
            finish[i] = isOk;
        }

        for (int i = 0; i < finish.length; i++) {
            if (finish[i]) {
                continue;
            }
            // 在未完成的進程中,找到一個可以分配資源的進程
            isOk = true;
            for (int j = 0; j < work.length; j++) {
                if (mNeed[i][j] > work[j]) {
                    isOk = false;
                    break;
                }
            }
            if (!isOk) {
                continue;
            }
            // 如果可以,就假設把資源分配給了它,且假設它運行完成了並退還了資源
            finish[i] = true;
            for (int j = 0; j < work.length; j++) {
                work[j] += mAllocated[i][j];
            }
            // 類似topo排序,空閒資源有退還,重新掃描
            i = 0;
        }
        // 最後檢查,是否所有的進程最後都可以安全結束
        for (boolean ans : finish) {
            if (!ans) {
                return false;
            }
        }
        return true;
    }

    /**
     * 檢查進程資源是否申請完畢,如果完畢則立即歸還資源
     *
     * @param res 資源池
     * @param k   進程編號
     */
    private void returnResIfFinished(int[] res, int k) {
        boolean isOk = true;
        for (int i = 0; i < res.length; i++) {
            if (mNeed[k][i] > 0) {
                isOk = false;
                break;
            }
        }
        // 退還進程名下資源
        if (isOk) {
            for (int i = 0; i < res.length; i++) {
                res[i] += mAllocated[k][i];
            }
        }
    }

    /**
     * 【測試用】
     * 用於外部各進程自己檢查是否資源申請完畢,申請完畢則假設立即歸還
     *
     * @param k 進程編號
     */
    public synchronized void returnResIfFinished(int k) {
        returnResIfFinished(mAvailable, k);
    }
}

以上。
2020.02.03
湖南.永州

發佈了84 篇原創文章 · 獲贊 21 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章