銀行家算法
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
:各進程還需要的資源數;
算法主流程
- 如果進程
i
請求資源j
大於進程i
還需要的資源數mNeed[i][j]
,則出錯;否則繼續下一步。 - 如果進程
i
請求資源j
大於當前該資源的空閒數mAvailable[i][j]
,則申請失敗進程等待;否則繼續下一步。 - 先執行分配,再檢查系統安全性,若安全則分配生效;否則,回滾分配。
檢查系統安全性
- 先檢查當前觸發安全性檢查的進程i是否已經完成分配,若已完成則假設其迅速歸還;否則不做處理繼續下一步。
- 從所有進程中找到未完成且當前資源可以爲其完成分配的進程,再假設已完成分配且其歸還所有資源;重複此步驟,直到再也找不到滿足條件的進程。
- 檢查所有進程的狀態,如果存在未標記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
湖南.永州