前言

刚学完操作系统,模拟实现了其中一些经典的算法,内容比较多,打算写一个系列的总结,将自己的源码都分享出来,既方便自己以后复习,也希望能帮助到一些刚入坑的小伙伴。我的所有代码的运行环境都是基于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> jcb;// 存放所有进程LinkedList<JCB> link;// 存放已经进入队列的进程ArrayList<JCB> new_jcb;// 存放按指定调度算法排序后的进程JCB nowProess;// 当前应执行进程public void init() {//初始化jcb = new ArrayList<JCB>();link = new LinkedList<JCB>();new_jcb = new ArrayList<JCB>();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<link.size();++i) {link.get(i).waitTime++;//所有进入等待队列的进程等待时间+1,此处只为最高响应比算法所用}}public void Dequeue(double sliceTime) {//重载Dequeue方法,实现轮转调度算法的出队nowProess = link.removeFirst();//移除队列的队首元素并且返回该对象元素if(nowProess.firstTimeTag==false) {/*轮转调度进程可能会多次反复进出队列,不像FCFS和SJF的进程只会进出一次,所以计算开始时间可以设个标志位,让每个进程在* 第一次执行时记录一遍即可*/nowProess.beginTime=nowTime;//进程开始执行的时间nowProess.firstTimeTag=true;//计算第一次即可,下次无需更新计算}nowTime+=sliceTime;//每次出队,用时一个时间片,更新当前时间nowProess.clock+=sliceTime;//更新当前出队列的进程已服务时间if(nowProess.clock>=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<JCB> {// 按服务时间升序public int compare(JCB arg0, JCB arg1) {return arg0.serveTime - arg1.serveTime;}
}class compareAt_St implements Comparator<JCB> {// 按到达时间升序,若到达时间相同,按服务时间升序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;} elsereturn -1;}
}
class comparePriority implements Comparator<JCB>{//按响应比升序排序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.实验结果与分析

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

调用展示结果:

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 先来先服务算法

5.2 短作业优先算法

5.3 时间片轮转调度算法

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

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

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

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

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

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

完整代码请参考,有帮助的话麻烦点个star哦

https://gitee.com/hxjjeremy/process

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

  1. 操作系统实验二——进程调度算法(FCFS、RR)

    目录 进程调度算法 FCFS算法代码 RR算法代码 进程调度算法 FCFS算法代码 #include <stdio.h> #include <string.h> #includ ...

  2. 进程调度算法——C++实现 [ FCFS,SJF,HPR,HRN + 开源代码 + 详细解析 ]

    ✅ (原创,库存,第100篇博客,纪念一下) 文章目录 零.动态演示图 一.实现原理 二.实现内容: 三.算法流程图: 3.1 先来先服务算法(FCFS)的流程图: 3.2 最短作业优先算法(SJF) ...

  3. 操作系统实验五:用户进程管理(详细分析)

    操作系统实验五:用户进程管理 一. 实验目的 二. 实验任务 三. 实验准备 1.alloc_proc() 函数 2.do_fork() 函数 3.idt_init() 函数 4.trap_dispa ...

  4. java读取XML文件的四种方式

    java读取XML文件的四种方式 Xml代码 <?xml version="1.0" encoding="GB2312"?> <RESULT& ...

  5. JAVA中集合输出的四种方式

    在JAVA中Collection输出有四种方式,分别如下: 一) Iterator输出. 该方式适用于Collection的所有子类. public class Hello {public stati ...

  6. 【零基础学Java】—final关键字与四种用法(二十九)

    [零基础学Java]-final关键字与四种用法(二十九) 一.final关键字 final关键字代表最终.不可改变的 常见的四种用法: 可以用来修饰一个类 可以用来修饰一个方法 可以用来修饰一个局部 ...

  7. java语言变量分为_在Java语言中变量分为四种,分别是___________________________________________。_学小易找答案...

    [填空题]One day, at the registrar's office of a college, I noticed how parents are behaving with their ...

  8. java中Map遍历的四种方式

    java中Map遍历的四种方式 在java中所有的map都实现了Map接口,因此所有的Map(如HashMap, TreeMap, LinkedHashMap, Hashtable等)都可以用以下的方 ...

  9. [转载]java抽取word,pdf的四种武器

    java抽取word,pdf的四种武器 很多人用java进行文档操作时经常会遇到一个问题,就是如何获得word,excel,pdf等文档的内容? 我研究了一下,在这里总结一下抽取word,pdf的几种 ...

最新文章

  1. 美宣布制裁中兴通讯 商务部:坚决反对并将交涉
  2. 3D模型在网页上显示
  3. JavaScript电话号码正则
  4. linux线程同步(2)-条件变量
  5. C语言 | 编写一个使用指针的c函数,交换数组a和数组b中的对应元素
  6. 《HTTP权威指南》学习笔记——HTTP报文
  7. (转)MFC技巧学习五
  8. 深度剖析RPC框架的核心设计
  9. Matlab 图像采集工具的使用 - Image Acquisition Toolbox【IAT】 + 大恒相机的应用【1】+多个摄像头支持
  10. Ubuntu 16.04配置CUDA 9.0+cudnn 7.0以及解决Nvidia显卡导致黑屏问题
  11. ASP.NET的属性绑定、表达式绑定、集合绑定、方法绑定、DropDownList集合绑定、DataList绑定、GridView绑定
  12. C++常见面试题-30道
  13. cs231n svm作业笔记
  14. rtnetlink组数量与设置
  15. 免安装mysql配置环境变量_mysql——免安装配置
  16. android死锁解决方案,【线程死锁】Android多线程死锁产生的原因以及如何避免
  17. 抖音、吃鸡、王者荣耀:你的自律,是如何被顶级产品经理一步一步毁掉的
  18. SonarQube速查手册
  19. Unity学习 — 官方中文版本教程详解
  20. 红外温度传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告

热门文章

  1. 架构师学习笔记14--信息系统基础知识
  2. vue element table列表删除某一页的最后一条数据之后页面不自动跳到上一页
  3. 中考物理化学能用计算机吗,中考物理化学总分多少分
  4. 谷歌地图Api部分使用
  5. miniui连接oracle,miniui从继承看控件处理
  6. 面向直播svga动画初学者,svga文件是什么
  7. 【视频目标检测论文阅读笔记】Optimizing Video Object Detection via a Scale-Time Lattice
  8. Mysql的连接分类和详解
  9. 今天把开源的webblogger: roller配起来了 [摘]
  10. 百度webupload的使用