当系统中多个进程或线程访问共享资源时,会因为资源数目过少而产生死锁问题,而死锁会引起计算机工作的僵死,因此在操作系统中必须减少死锁的发生。
而在操作系统,预防死锁的方法则是通过破坏产生死锁的四个必要条件中的一个或几个,这四个条件分别是:1.互斥条件 2.请求和保持条件 3.不可抢占条件 4.循环等待条件
而由于互斥条件是非共享设备所必须的,不仅不能改变,还应该加以保证,因此主要是破坏产生死锁的后三个条件。
除了预防死锁外,我们还可以采用避免死锁的方法减少死锁的发生(注意预防死锁与避免死锁还是有区别的)。而避免死锁中则主要使用我们接下来将要讲到的银行家算法。
1.算法介绍:
银行家算法是最具有代表性的避免死锁的算法。起这样的名字是由于该算法原本是为银行系统设计的,以确保银行在发行现金贷款是,不会发生不能满足所有客户需要的情况。在OS中也可以用它来实现避免死锁。
为实现银行家算法,每一个新进程在进入系统是,它必须申明在运行过程中,可能需要某种资源类型的最大单元数目,其数目不应超过系统所拥有的资源总量。当进程请求一组资源时,系统必须首先确定是否有足够的资源分配给该进程。若有,再进一步计算在将这些资源分配给该进程后,是否会使系统处于不安全的状态。如果不会,才将资源分配给它,否则让进程等待。
2.数据结构:
1)可利用资源向量Available ,它是一个含有m个元素的数组,其中的每一个元素代表一类可利用的资源的数目,其初始值是系统中所配置的该类全部可用资源数目。其数值随该类资源的分配和回收而动态地改变。如果Available(j)=k,标是系统中现有Rj类资源k个。
2)最大需求矩阵Max,这是一个n×m的矩阵,它定义了系统中n个进程中的每一个进程对m类资源的最大需求。如果Max(i,j)=k,表示进程i需要Rj类资源的最大数目为k。
3)分配矩阵Allocation,这是一个n×m的矩阵,它定义了系统中的每类资源当前一分配到每一个进程的资源数。如果Allocation(i,j)=k,表示进程i当前已经分到Rj类资源的数目为k。Allocation i表示进程i的分配向量,有矩阵Allocation的第i行构成。
4)需求矩阵Need,这是一个n×m的矩阵,用以表示每个进程还需要的各类资源的数目。如果Need(i,j)=k,表示进程i还需要Rj类资源k个,才能完成其任务。Need i表示进程i的需求向量,由矩阵Need的第i行构成。
上述三个矩阵间存在关系:Need(i,j)=Max(i,j)-Allocation(i,j);
3.银行家算法
设Requesti是进程Pi的请求量,如果Requesti[j]=k,表示进程Pi需要K个Rj类型的资源。当Pi发出资源请求后,系统按下述步骤进行检查:
1) 如果Requesti[j]<=Need[i,j], 便转向步骤2);否则认为出错,因为它所需要的资源数已超过它所需要的最大值。
2) 如果Requesti[j]<=Available[j],便转向步骤3);否则,表示尚无足够资源,Pi需等待。
3) 系统试探着把资源分配给进程Pi,并修改下面的数据结构中的数值:
Available[j] = Available[j] - Requesti[j];
Allocation[i][j] = Allocation[i][j] + Requesti[j];
Need[i][j] = Need[i][j] - Requesti[j];
4) 系统执行安全性算法,检查此次资源分配后系统是否处于安全状态。若安全,才正式将资源分配给进程Pi,以完成本次分配;否则将本次的试探分配作废,恢复原来的资源分配状态,让Pi等待。
4.安全性算法
1).设置两个向量。
Work:它表示系统可提供给进程继续运行的各类资源数目,它包含m个元素,开始执行安全性算法时,Work = Available。
Finish:它表示系统是否有足够的资源分配给进程,使之运行完成,开始Finish[i]=false;当有足够资源分配给进程Pi时,令Finish[i]=true;
2).从进程集合中找到一个能满足下述条件的进程。
Finish[i]= = false;
Need [i,j]<=Work[j];//j为所有的资源编号
如找到则执行步骤3;否则,执行步骤4;
3).当进程Pi获得资源后,可顺利执行直到完成,并释放出分配给它的资源,故应执行:
Work[j] = Work[j] + Allocation[i,j]; //j为所有的资源编号
Finish[i]=true;
转向步骤2;
4).若所有进程的Finish[i]都为true,则表示系统处于安全状态;否则,系统处于不安全状态。
5.实验流程图:
第一次写博客,比较谨慎,好了!不废话,直接上源代码:
(这里考虑到本博客的主要目的是了解银行家算法,并能用代码实现,所以这里我将所有的数据都是写死的,这样不仅便于调试,而且更能突出本实验的核心-银行家算法,而不用过多地去考虑复杂的输入输出格式)
package 操作系统_银行家算法;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Scanner;
public class Main {
public static int[][] Max = { { 7, 5, 3 }, { 3, 2, 2 }, { 9, 0, 2 }, { 2, 2, 2 }, { 4, 3, 3 } };
public static int[][] Allocation = { { 0, 1, 0 }, { 2, 0, 0 }, { 3, 0, 2 }, { 2, 1, 1 }, { 0, 0, 2 } };
public static int[][] Need = { { 7, 4, 3 }, { 1, 2, 2 }, { 6, 0, 0 }, { 0, 1, 1 }, { 4, 3, 1 } };
public static int[] Available = { 3, 3, 2 };
public static int[] Work = { 3, 3, 2 };
public static boolean[] Finished = { false, false, false, false, false };
public static LinkedList<Integer> sortedPCB = new LinkedList<Integer>();
public static int waysNumber = 0;
public static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("选择你想进行的操作:");
System.out.println("1)对某个进程进行资源分配 . 2)对当前所有进行进程安全性检查");
int choice = scanner.nextInt();
if (choice == 1)
ResourceRequest();
else {
System.out.println("当前系统存在以下安全序列:");
JudgeCurrentIsSafe(0);
if (waysNumber <= 0) {
System.out.println("不存在安全序列,当前系统存在死锁问题,是不安全的!!!!!!");
}
}
}
private static void ResourceRequest() {
System.out.println("请输入进行资源请求的线程号:(下标从0开始)");
int pcbNum = scanner.nextInt();
System.out.println("请输入该进程的请求向量,用空格隔开");
for (int j = 0; j < Work.length; j++) {
int temp = scanner.nextInt();
if (temp > Need[pcbNum][j]) {
System.out.println("第" + (j + 1) + "个请求资源大于所需的资源数,请退出程序重新输入");
// return;
}
if (temp > Available[j]) {
System.out.println("第" + (j + 1) + "个请求资源大于系统现有的资源数,请退出程序重新输入");
// return;
}
// 输入资源向量正确,开始资源分配:
Available[j] = Available[j] - temp;
Allocation[pcbNum][j] = Allocation[pcbNum][j] + temp;
Work[j] = Work[j] - temp;
Need[pcbNum][j] = Need[pcbNum][j] - temp;
}
System.out.println("若满足该进程的资源请求,则系统存在以下安全序列:");
JudgeCurrentIsSafe(0);
if (waysNumber <= 0) {
System.out.println("不存在安全序列,满足资源请求后存在死锁问题,是不安全的!!!!!!");
}
return;
}
private static void JudgeCurrentIsSafe(int FinfishNumber) {
// 已经找到一种安全序列,输出该序列
if (FinfishNumber >= Max.length) {
waysNumber++;
Iterator itera = sortedPCB.iterator();
while (itera.hasNext()) {
System.out.print(itera.next() + " ");
}
System.out.println();
return;
}
// 找到一个Finished状态为false,并且Nedd[i,j]<Work[j](j=1,2,3.....)
// i表示进程号,j表示资源号
for (int i = 0; i < Need.length; i++) {
// 当前查找的进程已经完成,则跳过
if (Finished[i])
continue;
boolean isOk = true;
for (int j = 0; j < Work.length; j++) {
// 如果该进程已经完成或者该进程的某个资源要求超过当前对应资源数,则不符合要求
if (Need[i][j] > Work[j]) {
isOk = false;
break;
}
}
// 如果当前进程满足分配要求,就将当前的资源分配给该资源
if (isOk) {
for (int j = 0; j < Work.length; j++)
Work[j] = Work[j] + Allocation[i][j];
Finished[i] = true;
sortedPCB.addLast(i);
FinfishNumber++;
JudgeCurrentIsSafe(FinfishNumber);
// 下面的工作很重要,如果当前不选择之前的进程进行资源分配,则撤销资源分配,回到地递归之前的状态
for (int j = 0; j < Need[0].length; j++)
Work[j] = Work[j] - Allocation[i][j];
Finished[i] = false;
FinfishNumber--;
sortedPCB.removeLast();
}
}
return;
}
}
运行结果:
1.当前系统安全性判断: 2.增加某进程的请求向量后的系统安全判断:
以上的代码主要有两个特点:
1.将对当前系统安全性判断和在增加某进程的请求向量后的系统安全判断进行整合。
2.在一般的银行家算法中增加了递归,将所有满足条件的安全序列全部输出,如果没有安全序列,则表明系统是不安全的,可能出现死锁。
ps:第一次发博文,还望各位大佬不吝赐教~