分时系统模拟(Java实现)
一、实验名称
实验(一)多道程序、进程、分时系统模拟
二、实验目的
加深对多道系统中进程管理的理解,了解进程的各种状态及其转换过程,分时系统中时间片的设置及进程在时间片开始和结束时的调度过程。
三、实验内容和要求
(1)利用程序设计语言模拟分时系统中多个进程按时间片轮转调度算法进行进程调度的过程;
(2)假设有五个进程A,B,C,D,E,它们的到达时间及要求服务的时间分别为:
进程名 A B C D E
到达时间 0 1 2 3 4
服务时间 4 3 4 2 4
时间片大小为1,利用程序模拟A,B,C,D,E五个进程按时间片轮转的调度及执行过程并计算周转时间及带权周转时间。
(3)时间片大小为2,利用程序模拟A,B,C,D,E五个进程按时间片轮转的调度及执行过程并计算周转时间及带权周转时间。
(4)时间片大小为4,利用程序模拟A,B,C,D,E五个进程按时间片轮转的调度及执行过程并计算周转时间及带权周转时间。
四、实验设计
package com.symc.dsaa.os.test1;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Scanner;
/**
* 分析:
* 第一,对于进程,每个进程有名称、到达时间、服务时间等属性,我们将它封装成对象Process,
* 并且添加好getter、setter方法和构造器。
* 第二,对于模拟这个进程的顺序,我们需要创建多个进程去测试,在执行的过程中,是轮流执行的,
* 每次只能执行一个进程,剩下的进程等待,需要一个数据结构去存放等待执行的进程,也即就绪队列,
* 我这里推荐使用队列(queue)
* 第三,队列的使用。队列是一个具有 先进先出 特性的线性结构,而我们用的就是它只能从队首出,从队尾进
* 的特点,放入队列的进程,使它做到经过时间片的处理,假设我这里的时间片是1秒,那处理后此进程的
* 服务时间就减去1秒,这里判断是否执行完,如果减完后小于或等于0,说明它执行完了,如果还大于0,
* 说明没执行完,这时就将它放到队尾,继续操作下一个进程。
* 第四,我们的实验中需要完成这四道题,它所对应的时间片不同,所以我们的程序应该灵活,能够同时实现
* 各种需求。
*/
public class TimeAround {
/**
* 变量介绍:
* ArrayList<Process> list:定义一个用于存放创建的进程的集合
* <p>
* int CPURun:用于记录CPU的执行时间,比如说,我们明确题目中5个进程,单线程运行的话,
* 一定是它们的服务时间的总和,也就是说,该程序运行时间为4+3+4+2+4=17
* <p>
* int num:我们可以自定义任务(进程)的数量
* <p>
* int[] serveTimes:用于存放进程的时间运行时间,也就是刚开始的服务时间,用于计算带权周转时间
*
* int timeSlice;定义时间片的大小
* <p>
* int finishedTime:进程的运行结束的时间,用于计算周转时间
* <p>
* double floatWi:带权周转时间,这里用double类型
* <p>
* ArrayDeque<Process> deque:定义队列,这是本次实验的核心的数据结构,在代码中有详细注释
*
* @param args
*/
public static void main(String[] args) {
ArrayList<Process> list = new ArrayList<>();
int CPURun = 0;//int CPURun:用于记录CPU的执行时间
int finishedTime = 0;
//int finishedTime:进程的运行结束的时间,用于计算周转时间
System.out.println("请输入进程数:");
int num = new Scanner(System.in).nextInt();
int[] serveTimes = new int[num];
//int[] serveTimes:用于存放进程的时间运行时间,用于计算带权周转时间
for (int i = 0; i < num; i++) {
System.out.println("请给第" + (i + 1) + "个进程起个名字:");
Process initProcess = Process.createInitProcess();
CPURun += initProcess.getServeTime();
serveTimes[i] = initProcess.getServeTime();
initProcess.setIndex(i);
list.add(initProcess);
}
System.out.println("请设置时间片大小(s):");
int timeSlice = new Scanner(System.in).nextInt();
//int timeSlice;定义时间片的大小
ArrayDeque<Process> deque = new ArrayDeque<Process>(num);
Process process;
for (int i = 0; i < CPURun; i++) {
if (i == 0) {
System.out.println("第" + i + "秒:");
}
//每隔一秒,向队列尾添加一个新的进程
if (i < num) {
process = list.get(i);
deque.offerLast(process);
System.out.println("进程" + process.getName() + "首次到达就绪队列");
}
System.out.println("进程" + deque.getFirst().getName() + "正在执行。。。");
System.out.println("当前就绪队列情况如下;");
for (Process task : deque) {
if (task.getName().equals(deque.getFirst())) {
continue;
}
System.out.print(task.getName() + "\t");
}
System.out.println();
System.out.println("----------------------------------------");
if (i != 0) {
System.out.println("第" + i + "秒:");
}
if (i % timeSlice != 0) {
continue;
}
//resetServeTime刷新服务时间,
Process process1 = deque.pollFirst().resetServeTime(timeSlice);
if (process1.isAchieved()) {
System.out.println("进程" + process1.getName() + "执行结束!");
finishedTime = i + process1.getServeTime();
System.out.println("进程" + process1.getName() + "的周转时间为:");
System.out.println(finishedTime - process1.getArriveTime());
System.out.println("进程" + process1.getName() + "的带权周转时间为:");
double floatWi =
(finishedTime - process1.getArriveTime()) / serveTimes[process1.getIndex()];
System.out.println(floatWi);
} else {
deque.offerLast(process1);
System.out.println("进程" + process1.getName() + "进入就绪队列");
}
}
}
}
package com.symc.dsaa.os.test1;
import java.util.Scanner;
public class Process {
private String name;
private int arriveTime;
private int serveTime;
private int index;
public Process() {
}
public Process(String name, int arriveTime, int serveTime) {
this.name = name;
this.arriveTime = arriveTime;
this.serveTime = serveTime;
}
public boolean isAchieved() {
if (serveTime <= 0) {
return true;
}
return false;
}
public static Process createInitProcess() {//创建初始化进程
Process process = new Process();
Scanner sc = new Scanner(System.in);
process.setName(sc.next());
System.out.println("请输入进程" + process.getName() + "的到达时间:");
process.setArriveTime(sc.nextInt());
System.out.println("请输入进程" + process.getName() + "的服务时间:");
process.setServeTime(sc.nextInt());
return process;
}
public Process resetServeTime(int timeSlice) {//时间片大小 当一个进程从队列中取出并执行完,期间经过了时间片的处理,服务时间缩减
this.serveTime = this.serveTime - timeSlice;
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getArriveTime() {
return arriveTime;
}
public void setArriveTime(int arriveTime) {
this.arriveTime = arriveTime;
}
public int getServeTime() {
return serveTime;
}
public void setServeTime(int serveTime) {
this.serveTime = serveTime;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
五、实验步骤及实验结果
1、实验内容
运行程序:根据题目要求输入数据
需要输入进程数,每个进程的名称,到达时间和服务时间,以及设置时间片的大小。
2、实验结果
六、实验中出现的问题及解决方法
问题1:程序实现的思想
在一开始,我考虑用的是实时的或者说动态的实现进程执行的根据时间片的顺序执行,并且使用了非阻塞队列的DelayQueue的集合。在这个方式中,是我对这个队列以及API的实现原理掌握得不透彻,导致遇到了很多问题,我甚至去重写该集合的源码来实现我的目的,还用到了对象克隆技术,还创建了两个安全性的线程,分别用来添加新的进程和被循环取出的进程到队列中。虽然实现列动态实时添加的效果,但是一些执行的效果并不好,由于我没有做到控制两个线程和主线程之间的顺序,导致结果显示得很难懂。
解决办法:我要的目的是实现这个模拟的结果,并不是注重这个过程,所以我只需要让它按照这个顺序跑出结果就可以了,并且,动态的每次执行需要等待,比如题中这5个任务一共是17秒的服务时间,那还需要等待17秒,才能看到我们想要的完整的结果。并且,使用普通的队列和单线程易控制,实现方式也简单。由此可见算法思想的重要性,此题将面向过程的思想转成面向结果的思想就简答多了。
问题2:有序地创建出进程(任务)以及进程的存储
我的想法是用户先将所有的进程设定好,然后一起跑(Run,运行的意思),但是设定好后的程序不能立即就使用,需要存放起来。
解决办法:将进程封装好后,我在此类中添加一个用于快速创建进程对象的静态方法,它的返回值就是根据用户的设定初始化好的进程。使用for循环批量创建,同时使用ArrayList将这些进程存储起来。
问题3:何时从队列中取出进程放到结尾
要知道正在执行的程序是按照时间片循环执行的,要让程序按照这个规定间隔的顺序去取出和放入就绪队列,也是一件麻烦的事情。刚开始我按照1秒钟的时间片去写程序,这个跑起来很流畅,但是换成2秒,4秒的话,它就不听话了。
解决办法:我加了这行代码:
if (i % timeSlice != 0) {
continue;
}
它是这么实现的,i是当前的时间,timeSlice是时间片,当前的时间点若是能被它是这么实现的,i是当前的时间,timeSlice是时间片,当前的时间点若是能被整除,说明此时正好是进程执行完一次的时间,后面就是交换进程的代码,如果要是不能被整除,说明此次时间片还没用完,结束本次循环,不让它跑后面的代码。比如,时间片大小为3,若i=6,那此时正好是第二轮时间片的结束时刻,此时应该交换进程;若i=5,不能整除,那不好意思,不能交换进程,需要再等一秒再说。
问题4:进程每次执行完后的处理以及放入就绪队列队尾
首先要知道它有没有执行完,若执行完就不用放到队尾,若没有执行完,那就得放到队尾,在放到队尾之前,对应的服务时间应该减去时间片。
解决办法:好在用的面向对象的方式,直接通过set方法去修改服务时间即可。对于判断是否执行完,我在进程的类中添加了一个boolean类型的方法,服务时间等于或者小于0就说明它执行完了。我还定义了一个reset方法使该进程的服务时间减少。队列的集合有相关取出元素和放入元素的API。
七、结论
时间片大小应适中。