Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)

前言

刚学完操作系统,模拟实现了其中一些经典的算法,内容比较多,打算写一个系列的总结,将自己的源码都分享出来,既方便自己以后复习,也希望能帮助到一些刚入坑的小伙伴。我的所有代码的运行环境都是基于Eclipse,jdk1.10下。

1.问题概述

编程实现常用调度算法,即先来先服务、短作业(进程)优先、时间片轮转以及最高响应比优先调度算法。编程语言及环境不限。须给出关键数据结构、算法以及变量的详细说明与注释。

2.算法简介

1、时间片轮转调度算法(RR):给每个进程固定的执行时间,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度。优点是兼顾长短作业;缺点是平均等待时间较长,上下文切换较费时。适用于分时系统。

2、先来先服务调度算法(FCFS):根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业。

3、高响应比优先调度算法(HRN):根据“响应比=(进程执行时间+进程等待时间)/ 进程执行时间”这个公式得到的响应比来进行调度。高响应比优先算法在等待时间相同的情况下,作业执行的时间越短,响应比越高,满足段任务优先,同时响应比会随着等待时间增加而变大,优先级会提高,能够避免饥饿现象。优点是兼顾长短作业,缺点是计算响应比开销大,适用于批处理系统。

4.短进程优先算法(SJF):以作业的长短来计算优先级,作业越短,其优先级越高。作业的长短是以作业所要求的运行时间来衡量的。

3.思路分析

一开始真的无从下手,想了半天就实现了个FCFS(还只是单纯的把容器内的作业按到达时间排个序号,其实想法是错误的,因为我无法预知到达的次序)。后来注意到了一个贯穿始终的东西——队列。顺着这条思路,通过一点一点深入的分析,一切仿佛豁然开朗,主体的框架写出后,后面就水到渠成了。

       首先是确定三个容器,第一个存放所有进程的信息(只是拿来遍历),第二个为队列进行主要的操作(各种算法下的进出队列操作),第三个用来保存已经处理过后的进程(输出结果,便于观察)。所有的核心都集中在了如何进出队列,更进一步的是如何在不同的规则下排序,所以只要定义不同算法的排序比较器,按这个来进出队列就成了。之后一些细节工作,比如分清是全局或局部变量,代码的先后执行次序,如果有可复用的代码就写成一个方法来调用等等,通过不断的调试修改一点点完善。

4.代码实现

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;
	}
	
}

5.实验结果与分析

测试数据为(怕有人看不到,就是初始化那段代码):

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第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();
	}
}

5.1 先来先服务算法

 Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第2张图片

5.2 短作业优先算法

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第3张图片

5.3 时间片轮转调度算法

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第4张图片

5.4最高响应比优先调度算法

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第5张图片

因为是一次性实现四个算法,为了降低代码的耦合,我做了一些优化,那个队列就像一个媒介,每个算法都可以用到,为了最大程度的代码复用就抽象成一个类。这里面细节太多,很多都不好描述,队列的进出过程都在上面了,一定要自己动动手把进出队列的图画画,再结合我的注释,帮助理解。

补充:我是用容器来写的,这样能省下很多繁琐的代码,比如我要比较并排列队列内的进程顺序,写个比较器就好了,清晰简洁明了,不要重复造轮子呀,分清主次!

完整代码请参考https://download.csdn.net/download/qq_37373250/12057741

 

 

=====================手动分割线========补充内容================================

楼下有人提出我写的RR算法有个bug,就是说在时间片为1的时候一切正常,时间片设为4时出错了,我来演示一下

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第6张图片

结果是这样的:

很显然错了,问题出在这:

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第7张图片

此时时间片为4,p1进来后不再执行else里面的语句,因为if(4>=4)为true,执行这语句块内代码,服务时间刚好用完,此时link队列为空(因为没有新进程入队,我的疏漏就在这),然后该方法执行完后回到RR算法内

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第8张图片

好了,此时link队列为空,那就没然后了,跳出循环,结束!

知道这个要改就容易了,加上这句话,假如进入的进程在时间片内就直接服务完了,就让剩下的进程入队

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第9张图片

时间片为4时,结果如下:

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第10张图片

=====================手动分割线========补充内容================================


看了下楼下指出的问题,bug很多,今天有空,就来一起解决一下。我首先看了下RR算法(时间片为1时),打了个草稿验算了一下,

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第11张图片

跟原来的结果对比观察

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第12张图片

发现我的队列排列顺序都错了,仔细想了想,是出队列的时候RR算法不同与FCFS和SJF,不是先进先出。

FCFS:

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第13张图片

RR:(若队首的进程时间片用完且还未完成就放到链尾,新来的进程放到队首,执行都是从队首取出来分给时间片的,我这里搞反了,也像FCFS一样放到链尾了,就错了)

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第14张图片

原来只有一个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算法的开始时间还是错的,这里是我漏了考虑了,可以加一个标志位判断是否第一次获得时间片执行,具体看注释:

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第15张图片

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第16张图片

这样一来就好了,结果如下,完全吻合手算结果

Java模拟操作系统实验一:四种进程调度算法实现(FCFS,SJF,RR,HRN)_第17张图片

因为进出队列的函数写错了,所以其他算法也受到了影响,现在以上代码,结果,以及源代码已全部改正,还有问题,欢迎指出!

你可能感兴趣的:(操作系统)