刚学完操作系统,模拟实现了其中一些经典的算法,内容比较多,打算写一个系列的总结,将自己的源码都分享出来,既方便自己以后复习,也希望能帮助到一些刚入坑的小伙伴。我的所有代码的运行环境都是基于Eclipse,jdk1.10下。
编程实现常用调度算法,即先来先服务、短作业(进程)优先、时间片轮转以及最高响应比优先调度算法。编程语言及环境不限。须给出关键数据结构、算法以及变量的详细说明与注释。
1、时间片轮转调度算法(RR):给每个进程固定的执行时间,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度。优点是兼顾长短作业;缺点是平均等待时间较长,上下文切换较费时。适用于分时系统。
2、先来先服务调度算法(FCFS):根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业。
3、高响应比优先调度算法(HRN):根据“响应比=(进程执行时间+进程等待时间)/ 进程执行时间”这个公式得到的响应比来进行调度。高响应比优先算法在等待时间相同的情况下,作业执行的时间越短,响应比越高,满足段任务优先,同时响应比会随着等待时间增加而变大,优先级会提高,能够避免饥饿现象。优点是兼顾长短作业,缺点是计算响应比开销大,适用于批处理系统。
4.短进程优先算法(SJF):以作业的长短来计算优先级,作业越短,其优先级越高。作业的长短是以作业所要求的运行时间来衡量的。
一开始真的无从下手,想了半天就实现了个FCFS(还只是单纯的把容器内的作业按到达时间排个序号,其实想法是错误的,因为我无法预知到达的次序)。后来注意到了一个贯穿始终的东西——队列。顺着这条思路,通过一点一点深入的分析,一切仿佛豁然开朗,主体的框架写出后,后面就水到渠成了。
首先是确定三个容器,第一个存放所有进程的信息(只是拿来遍历),第二个为队列进行主要的操作(各种算法下的进出队列操作),第三个用来保存已经处理过后的进程(输出结果,便于观察)。所有的核心都集中在了如何进出队列,更进一步的是如何在不同的规则下排序,所以只要定义不同算法的排序比较器,按这个来进出队列就成了。之后一些细节工作,比如分清是全局或局部变量,代码的先后执行次序,如果有可复用的代码就写成一个方法来调用等等,通过不断的调试修改一点点完善。
package Process;
public class JCB {
String name;//进程名
int arriveTime;//到达时间
int serveTime;//服务时间
int beginTime;//开始时间
int finshTime;//结束时间
int roundTime;//周转时间
double aveRoundTime;//带权周转时间
double clock=0;//在时间轮转调度算法中,记录该进程真实服务时间已经用时的时长
int waitTime;//记录每个进程到达后的等待时间,只用于最高响应比优先调度算法中
boolean firstTimeTag=false;//在RR算法中标识开始时间是否第一次计算
public JCB() {
}
public JCB(String name, int arriveTime, int serveTime,double priority) {
super();
this.name = name;
this.arriveTime = arriveTime;
this.serveTime = serveTime;
this.waitTime=0;
}
public String toString() {
String info=new String("进程名:P"+this.name);
return info;
}
}
package Process;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
public class processMenu {
ArrayList jcb;// 存放所有进程
LinkedList link;// 存放已经进入队列的进程
ArrayList new_jcb;// 存放按指定调度算法排序后的进程
JCB nowProess;// 当前应执行进程
public void init() {//初始化
jcb = new ArrayList();
link = new LinkedList();
new_jcb = new ArrayList();
JCB p1 = new JCB("1", 0, 4,3);
JCB p2 = new JCB("2", 1, 3,2);
JCB p3 = new JCB("3", 2, 5,3);
JCB p4 = new JCB("4", 3, 2,1);
JCB p5 = new JCB("5", 4, 4,5);
jcb.add(p1);jcb.add(p2);jcb.add(p3);jcb.add(p4);jcb.add(p5);
//先将jcb排序,便于下面的算法实现,就不需要再定义一个标识进程是否已到达的boolean,即无需每次都从头开始扫描jcb容器,
//而是用一个K记录下当前已经扫描到的位置,一次遍历即可,提高了算法效率。
Collections.sort(jcb, new compareAt_St());
}
public void FCFS(){//先来先服务算法
ProcessQueue pq=new ProcessQueue();//调用内部类
pq.EnqueueLast();//让最先到达的进程先入队
System.out.println("*****************************************************");
while(!link.isEmpty()) {//while(new_jcb.size()!=jcb.size())
pq.DisplayQueue();//打印当前队列中的进程
pq.Dequeue();//出队,一次一个
pq.EnqueueLast();//已到达的进程入队
}
}
public void SJF() {// 短作业优先算法
ProcessQueue pq=new ProcessQueue();
pq.EnqueueLast();
System.out.println("*****************************************************");
while(!link.isEmpty()) {
pq.DisplayQueue();//打印当前队列中的进程
pq.Dequeue();//出队,一次一个
pq.EnqueueLast();//已到达的进程入队
Collections.sort(link, new compareSt());//队列中的进程还需按服务时间长度进行排序
}
}
public void RR() {//时间片轮转调度算法
ProcessQueue pq=new ProcessQueue();
pq.EnqueueLast();
System.out.println("*****************************************************");
while(!link.isEmpty()) {
pq.DisplayQueue();//打印当前队列中的进程
pq.Dequeue(1);//出队,一次一个,因为上一轮出的得让刚到达的进程先进队列,所以没办法,进队操作只能也放在这个函数里了
}
}
public void HRN() {//最高响应比优先调度算法
ProcessQueue pq=new ProcessQueue();
pq.EnqueueLast();
System.out.println("*****************************************************");
while(!link.isEmpty()) {
pq.DisplayQueue();//打印当前队列中的进程
pq.Dequeue();//出队,一次一个
pq.EnqueueLast();//已到达的进程入队
Collections.sort(link, new comparePriority());//队列中的进程还需按响应比进行排序
}
}
class ProcessQueue{
int k = 0;// jcb中的进程遍历时的下标
int nowTime = 0;// 当前时间
double sliceTime;//轮转调度时间片
int i=0;//记录当前出入队列的次数
public void EnqueueLast() {//进程首次入队,可一次进多个,从队尾进入
while (k < jcb.size()) {//当遍历完jcb中的所有进程时结束
if (jcb.get(k).arriveTime <= nowTime) {//已经到达的进程按到达时间先后进入队列
link.addLast(jcb.get(k));
k++;
} else {
break;//如果该进程还未入队,即先结束遍历,保留当前下标k值,注意:此处不要k--;
}
}
}
public void EnqueueFirst() {//进程首次入队,可一次进多个,从队首进入
while (k < jcb.size()) {//当遍历完jcb中的所有进程时结束
if (jcb.get(k).arriveTime <= nowTime) {//已经到达的进程按到达时间先后进入队列
link.addFirst(jcb.get(k));
k++;
} else {
break;//如果该进程还未入队,即先结束遍历,保留当前下标k值,注意:此处不要k--;
}
}
}
public void Dequeue() {//进程出队,一次只出一个
nowProess = link.removeFirst();//移除队列的队首元素并且返回该对象元素
nowProess.beginTime = nowTime;//计算开始时间,即为上一个进程的结束时间
nowProess.finshTime = nowProess.beginTime + nowProess.serveTime;//计算结束时间,该进程开始时间+服务时间
nowProess.roundTime = nowProess.finshTime - nowProess.arriveTime;//计算周转时间
nowProess.aveRoundTime = (double) nowProess.roundTime / nowProess.serveTime;//计算平均周转时间
nowTime = nowProess.finshTime;//获得结束时间,即当前时间,方便判断剩下的进程是否已到达
new_jcb.add(nowProess);//经处理过数据后加入new_jcb容器
for(int i=0;i=nowProess.serveTime) {
nowProess.finshTime=nowTime;//计算该进程完成时间
nowProess.roundTime = nowProess.finshTime - nowProess.arriveTime;//计算周转时间
nowProess.aveRoundTime = (double) nowProess.roundTime / nowProess.serveTime;//计算平均周转时间
new_jcb.add(nowProess);//经处理过数据后加入new_jcb容器
EnqueueFirst();//已到达的进程先入队
}
else {
EnqueueFirst();//已到达的进程先入队
link.addLast(nowProess);//上一轮出的再紧接着进入队尾
}
}
public void DisplayQueue(){//队列中等候的进程
i++;
System.out.println("第"+i+"次队列中排队的进程:"+link);
}
}
public void printProcess() {
System.out.println("进程名 到达时间 服务时间 开始时间 完成时间 周转时间 带权周转时间");
for (int i = 0; i < new_jcb.size(); ++i) {
System.out.println("P"+new_jcb.get(i).name + " " + new_jcb.get(i).arriveTime + " " +
new_jcb.get(i).serveTime+ " " + new_jcb.get(i).beginTime + " " + new_jcb.get(i).finshTime +
" "+ new_jcb.get(i).roundTime + " " + new_jcb.get(i).aveRoundTime);
}
new_jcb.clear();//清空new_jcb容器内的内容,方便存储各种算法的结果并展示
}
}
class compareSt implements Comparator {// 按服务时间升序
public int compare(JCB arg0, JCB arg1) {
return arg0.serveTime - arg1.serveTime;
}
}
class compareAt_St implements Comparator {// 按到达时间升序,若到达时间相同,按服务时间升序
public int compare(JCB o1, JCB o2) {
int a = o1.arriveTime - o2.arriveTime;
if (a > 0)
return 1;
else if (a == 0) {
return o1.serveTime > o2.serveTime ? 1 : -1;
} else
return -1;
}
}
class comparePriority implements Comparator{//按响应比升序排序
public int compare(JCB o1, JCB o2) {
double r1=(double)o1.waitTime/o1.serveTime;
double r2=(double)o2.waitTime/o2.serveTime;
return r1>r2?1:-1;
}
}
测试数据为(怕有人看不到,就是初始化那段代码):
调用展示结果:
public class TestProcess {
public static void main(String[] args) {
processMenu pm=new processMenu();
pm.init();//初始化容器
pm.FCFS();pm.printProcess();
pm.SJF();pm.printProcess();
pm.RR();pm.printProcess();
pm.HRN();pm.printProcess();
}
}
因为是一次性实现四个算法,为了降低代码的耦合,我做了一些优化,那个队列就像一个媒介,每个算法都可以用到,为了最大程度的代码复用就抽象成一个类。这里面细节太多,很多都不好描述,队列的进出过程都在上面了,一定要自己动动手把进出队列的图画画,再结合我的注释,帮助理解。
补充:我是用容器来写的,这样能省下很多繁琐的代码,比如我要比较并排列队列内的进程顺序,写个比较器就好了,清晰简洁明了,不要重复造轮子呀,分清主次!
完整代码请参考https://download.csdn.net/download/qq_37373250/12057741
=====================手动分割线========补充内容================================
楼下有人提出我写的RR算法有个bug,就是说在时间片为1的时候一切正常,时间片设为4时出错了,我来演示一下
结果是这样的:
很显然错了,问题出在这:
此时时间片为4,p1进来后不再执行else里面的语句,因为if(4>=4)为true,执行这语句块内代码,服务时间刚好用完,此时link队列为空(因为没有新进程入队,我的疏漏就在这),然后该方法执行完后回到RR算法内
好了,此时link队列为空,那就没然后了,跳出循环,结束!
知道这个要改就容易了,加上这句话,假如进入的进程在时间片内就直接服务完了,就让剩下的进程入队
时间片为4时,结果如下:
=====================手动分割线========补充内容================================
看了下楼下指出的问题,bug很多,今天有空,就来一起解决一下。我首先看了下RR算法(时间片为1时),打了个草稿验算了一下,
跟原来的结果对比观察
发现我的队列排列顺序都错了,仔细想了想,是出队列的时候RR算法不同与FCFS和SJF,不是先进先出。
FCFS:
RR:(若队首的进程时间片用完且还未完成就放到链尾,新来的进程放到队首,执行都是从队首取出来分给时间片的,我这里搞反了,也像FCFS一样放到链尾了,就错了)
原来只有一个Enquene,用来队尾插入,现在改为:
public void EnqueueLast() {//进程首次入队,可一次进多个,从队尾进入
while (k < jcb.size()) {//当遍历完jcb中的所有进程时结束
if (jcb.get(k).arriveTime <= nowTime) {//已经到达的进程按到达时间先后进入队列
link.addLast(jcb.get(k));
k++;
} else {
break;//如果该进程还未入队,即先结束遍历,保留当前下标k值,注意:此处不要k--;
}
}
}
public void EnqueueFirst() {//进程首次入队,可一次进多个,从队首进入
while (k < jcb.size()) {//当遍历完jcb中的所有进程时结束
if (jcb.get(k).arriveTime <= nowTime) {//已经到达的进程按到达时间先后进入队列
link.addFirst(jcb.get(k));
k++;
} else {
break;//如果该进程还未入队,即先结束遍历,保留当前下标k值,注意:此处不要k--;
}
}
}
这样就好了,但是还有一个问题是RR算法的开始时间还是错的,这里是我漏了考虑了,可以加一个标志位判断是否第一次获得时间片执行,具体看注释:
这样一来就好了,结果如下,完全吻合手算结果
因为进出队列的函数写错了,所以其他算法也受到了影响,现在以上代码,结果,以及源代码已全部改正,还有问题,欢迎指出!