Linux操作系统课程设计 - 使用JAVA实现时间片轮转法
题目
实验2:时间片轮转法
基本思想:将CPU 的处理时间划分成一个个时间片,就绪队列中的诸进程轮流运行一个时间片,当时间片结束时,就强迫运行进程让出CPU,该进程进入就绪队列,等待下一次调度,同时,进程调度又去选择就绪队列中的一个进程,分配给它一个时间片,以投入运行。在轮转法中,时间片长度的选择非常重要,将直接影响系统开销和响应时间。如果时间片长度很小,则调度程序剥夺处理机的次数频繁,加重系统开销;反之,如果时间片长度选择过长,比方说一个时间片就能保证就绪队列中所有进程都执行完毕,则轮转法就退化成先进先出算法。
结论可以归结如下:时间片设得太短会导致过多的进程切换,降低了CPU效率;而设得太长又可能引起对短的交互请求的响应变差。将时间片设为100毫秒通常是一个比较合理的折衷。
注意事项
题目中提到的是100毫秒,本文采用的是书中的一组数据,是以一个时间单位计算
作业情况 | 进程名 | A | B | C | D | E | 平均值 |
---|---|---|---|---|---|---|---|
到达时间 | 0 | 1 | 2 | 3 | 4 | ||
服务时间 | 4 | 3 | 4 | 2 | 4 | ||
q=1 | 完成时间 | 12 | 10 | 16 | 11 | 17 | |
周转时间 | 12 | 9 | 14 | 8 | 13 | 11.2 | |
带权周转时间 | 3 | 3 | 3.5 | 4 | 3.25 | 3.35 | |
q=4 | 完成时间 | 4 | 7 | 11 | 13 | 17 | |
周转时间 | 4 | 6 | 9 | 10 | 13 | 8.4 | |
带权周转时间 | 1 | 2 | 2.25 | 5 | 3.25 | 2.7 |
q=1 and q=4
A 0 4
B 1 3
C 2 4
D 3 2
E 4 4
为了符合题目的要求建议使用如下数据
q=100 and q=400
A 0 400
B 100 300
C 200 400
D 300 200
E 400 400
代码
带注释版本
package mypackage;
import java.text.DecimalFormat;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
/**
* @author 石义山
* @since 2023/06/03
*/
public class RoundRobin {
static int processArrayLength;// 用于存储进程的数组的长度
static int timeSlice;// 时间片长度
static Process[] processArray;// 存储输入的进程信息后的实例,主要打印方便,下方三个队列才是主要的操作对象
static Queue<Process> taskQueue = new PriorityQueue<>();// 任务队列
static Queue<Process> readyQueue = new LinkedList<>();// 就绪队列
static Queue<Process> completeQueue = new LinkedList<>();// 完成队列
static double sumTurnRoundTime = 0; // 所有进程周转时间之和,为了计算之后的平均值
static double sumWeightTurnRoundTime = 0;// 所有带权周转时间之和,为了计算之后的平均值
/**
* 该方法根据用户的输入,初始化任务队列和时间片长度
*/
public static void taskQueueInit() {
Scanner scanner = new Scanner(System.in);
System.out.println("时间片轮转算法\n请设置时间片长度q = ");
timeSlice = scanner.nextInt();
System.out.println("请输入进程数量");
processArrayLength = scanner.nextInt();
processArray = new Process[processArrayLength];// 根据进程的数量初始化存储进程的数组
System.out.println("请输入每一个进程的进程名、到达时间、服务时间(一行一个进程,进程名、到达时间、服务时间中间用空格区分)");
for (int i = 0; i < processArrayLength; i++) {
String pName = scanner.next();
int pArriveTime = scanner.nextInt();
int pServeTime = scanner.nextInt();
Process p = new Process(pName, pArriveTime, pServeTime);
processArray[i] = p;
taskQueue.offer(p);// 把每个进程推入队列
}
scanner.close();
}
/**
* 该方法用于打印正处于CPU计算中的进程的信息,打印就绪队列中的处于等待的进程名称,该方法在每次时间片结束时调度一次
*
* @param computingProcess 处于CPU中进程
* @param timeAxis 甘特图的时间轴
* @param readyQueue 就绪队列
* @param usedTime 正在计算的进程占用CPU的时间
*/
public static void printCpuInfo(Process computingProcess, int timeAxis, Queue<Process> readyQueue, int usedTime) {
String readyProcessesName = "";// 已就绪的进程的名字,当存在多个进程时使用字符串拼接
if (readyQueue.peek() == null) {
readyProcessesName = null;
} else {
for (Process p : readyQueue) {
readyProcessesName = readyProcessesName + p.name + " ";
}
}
System.out.print("时间[ " + (timeAxis - usedTime) + " , " + timeAxis + " )内,进程" + computingProcess.name + " 执行,就绪队列为 ");
System.out.print(readyProcessesName == null ? "null" : readyProcessesName);
System.out.println();
}
/**
* 该方法判断进程是否到达,是则从任务队列压入就绪队列
*
* @param taskQueue 任务队列
* @param timeAxis 时间轴,理解为当前时间
*/
public static void handleReadyQueue(Queue<Process> taskQueue, int timeAxis) {
while (true) {
// 每次循环都将任务队列队首的进程复制一份
Process p = taskQueue.peek();
// 进程全部就绪的情况,如果无法任务队列中取出对象,则说明任务队列已经全部加入就绪队列或者执行完毕,则退出循环
if (p == null) {
break;
}
// 进程按时到达的情况,如果时间轴>=进程的到达时间,则将任务队列队首进程添加到就绪队列。
// && p.serveTime > 0为了避免无效的任务出现在就绪队列中
if (timeAxis >= p.arriveTime && p.serveTime > 0) {
readyQueue.offer(taskQueue.poll());// 在确定任务已经到达时,才将任务队列队首的元素取出压入就绪队列
} else {
// 进程没有到达的情况,因为任务队列是一个优先队列,如果队首的进程都没有到达,则说明后续的进程也没到达,则退出循环
break;
}
}
}
/**
* 该方法用于计算周转时间以及计算带权周转时间
*
* @param process 根据当前的进程
*/
public static void calcTurnRoundTime(Process process) {
process.turnRoundTime = (double) process.completeTime - (double) process.arriveTime;
DecimalFormat df = new DecimalFormat("0.00");
double temp = (double) process.turnRoundTime / (double) process.serveTime;
process.weightedTurnRoundTime = Double.parseDouble(df.format(temp));
sumTurnRoundTime += process.turnRoundTime;
sumWeightTurnRoundTime += process.weightedTurnRoundTime;
}
public static void run(Queue<Process> taskQueue, int timeSlice) {
int timeAxis = 0;// 时间轴,或者看做物理时间
int availableTime = 0;// 进程执行一次可以使用CPU的时间
Process computingProcess = null;// 正在计算的进程
// 当时间=0时,需要初始化一次就绪队列
handleReadyQueue(taskQueue, timeAxis);
while (completeQueue.size() < processArrayLength) {
while (readyQueue.peek() == null) {
// 如果就绪队列队首为空,则说明没有进程到达,则让时间增加,调用handleReadyQueue()直到就绪队列中有进程为止。
timeAxis++;
handleReadyQueue(taskQueue, timeAxis);
}
availableTime = timeSlice;// 每次循环都需要重置一次可用时间
computingProcess = readyQueue.poll();// 取出就绪队列队首元素,放入CPU线程
// 这个条件判断用于初始化Process对象的startTime,如果开始时间为0,且服务时间==备份的服务时间,则说明这个进程第一次进入CPU,
// 服务时间==备份的服务时间判断的作用是,避免该进程是第0时刻到达的进程,
// 如果不做这个判断,当第0时刻到达的进程第二次被CPU处理时又会再执行一次if中的代码
if (computingProcess.startTime == 0 && computingProcess.copyServeTime == computingProcess.serveTime) {
computingProcess.startTime = timeAxis;
}
// 添加标签,用于跳出循环
outer:
do {
timeAxis++;
availableTime--;
computingProcess.copyServeTime--;
// 处理进程在时间片内完成的情况,意味着当前进程执行到此处刚好结束
if (computingProcess.copyServeTime <= 0) {
// 先处理异常情况,这里为了方便直接打印,未做异常处理
if (computingProcess.copyServeTime < 0) {
System.out.println("输入错误,进程的服务时间不能<0");
System.exit(0);
}
// 将cpu中的进程完成后,将其添加到完成队列
completeQueue.offer(computingProcess);
computingProcess.completeTime = timeAxis;
// 处理一次就绪队列
handleReadyQueue(taskQueue, timeAxis);
// 打印进程的运行信息,如果|可用时间-时间片|<时间片,则取可用时间-时间片得绝对值,就是用掉的时间,否则=时间片则说明时间片用完
int usedTime = Math.abs(availableTime - timeSlice) == timeSlice ? timeSlice : Math.abs(availableTime - timeSlice);
printCpuInfo(computingProcess, timeAxis, readyQueue, usedTime);
// 因为进程提前结束,为避免异常情况出现,所以此处对可用时间置为0
availableTime = 0;
System.out.println("进程" + computingProcess.name + "执行完毕");
// 计算周转时间和带权周转时间,并跳转处此次循环
calcTurnRoundTime(computingProcess);
break outer;
} else {
// 进程正常执行的情况,时间每增加一个单位则加载一次就绪队列
handleReadyQueue(taskQueue, timeAxis);
// 时间片用完的情况
if (availableTime <= 0) {
// 处理异常情况
if (availableTime < 0) {
System.out.println("进程的可用时间不能<0");
System.exit(0);
}
// 当前时间片用完,打印当前时间片的进程信息,并让CPU中的进程返回就绪队列的队尾
printCpuInfo(computingProcess, timeAxis, readyQueue, timeSlice);
readyQueue.offer(computingProcess);
break outer;
}//如果可用时间>0则让CPU中的进程继续执行
}
} while (availableTime > 0);
//当一个进程执行完毕或者时间片用完时,需要让出CPU进程
computingProcess = null;
}
}
// 打印输出结果
public static void resultOutput() {
System.out.println("----------------------------------------------------------------");
System.out.println("作业名\t到达时间\t服务时间\t开始时间\t完成时间\t周转时间\t带权周转时间");
for (Process p : completeQueue) {
System.out.println(p.name + "\t\t" +
p.arriveTime + "\t\t" +
p.serveTime + "\t\t" +
p.startTime + "\t\t" +
p.completeTime + "\t\t" +
p.turnRoundTime + "\t\t\t" +
p.weightedTurnRoundTime);
}
DecimalFormat df = new DecimalFormat("0.00");
double avgTRTime = Double.parseDouble(df.format(sumTurnRoundTime / processArrayLength));
double avgWTRTime = Double.parseDouble(df.format(sumWeightTurnRoundTime / processArrayLength));
System.out.println("平均周转时间为:" + avgTRTime + "\t平均带权周转时间为:" + avgWTRTime);
}
public static void main(String[] args) {
taskQueueInit();
for (Process p : taskQueue) {
System.out.println(p);
}
System.out.println("----------------------------------------------------------------");
run(taskQueue, timeSlice);
resultOutput();
}
}
class Process implements Comparable<Process> {
public String name;// 进程名
public int arriveTime;// 到达时间
public int serveTime;// 服务时间,该服务时间用于打印
public int copyServeTime;// 备份一个服务时间,用来进行值的操作
public int startTime = 0;// 进程开始执行时间
public int completeTime;// 进程完成时间
public double turnRoundTime;// 周转时间
public double weightedTurnRoundTime;// 带权周转时间
public Process(String name, int arrive, int serve) {
this.name = name;
this.arriveTime = arrive;
this.serveTime = serve;
this.copyServeTime = serve;
}
@Override
public int compareTo(Process process) {
return this.arriveTime - process.arriveTime;
}
@Override
public String toString() {
return "进程名:" + name +
", 到达时间:" + arriveTime +
", 服务时间:" + serveTime;
}
}
运行结果如下
时间片轮转算法
请设置时间片长度q =
1
请输入进程数量
5
请输入每一个进程的进程名、到达时间、服务时间(一行一个进程,进程名、到达时间、服务时间中间用空格区分)
A 0 4
B 1 3
C 2 4
D 3 2
E 4 4
----------------------------------------------------------------
进程名:A, 到达时间:0, 服务时间:4
进程名:B, 到达时间:1, 服务时间:3
进程名:C, 到达时间:2, 服务时间:4
进程名:D, 到达时间:3, 服务时间:2
进程名:E, 到达时间:4, 服务时间:4
----------------------------------------------------------------
时间[ 0 , 1 )内,进程A 执行,就绪队列为 B
时间[ 1 , 2 )内,进程B 执行,就绪队列为 A C
时间[ 2 , 3 )内,进程A 执行,就绪队列为 C B D
时间[ 3 , 4 )内,进程C 执行,就绪队列为 B D A E
时间[ 4 , 5 )内,进程B 执行,就绪队列为 D A E C
时间[ 5 , 6 )内,进程D 执行,就绪队列为 A E C B
时间[ 6 , 7 )内,进程A 执行,就绪队列为 E C B D
时间[ 7 , 8 )内,进程E 执行,就绪队列为 C B D A
时间[ 8 , 9 )内,进程C 执行,就绪队列为 B D A E
时间[ 9 , 10 )内,进程B 执行,就绪队列为 D A E C
进程B执行完毕
时间[ 10 , 11 )内,进程D 执行,就绪队列为 A E C
进程D执行完毕
时间[ 11 , 12 )内,进程A 执行,就绪队列为 E C
进程A执行完毕
时间[ 12 , 13 )内,进程E 执行,就绪队列为 C
时间[ 13 , 14 )内,进程C 执行,就绪队列为 E
时间[ 14 , 15 )内,进程E 执行,就绪队列为 C
时间[ 15 , 16 )内,进程C 执行,就绪队列为 E
进程C执行完毕
时间[ 16 , 17 )内,进程E 执行,就绪队列为 null
进程E执行完毕
----------------------------------------------------------------
作业名 到达时间 服务时间 开始时间 完成时间 周转时间 带权周转时间
B 1 3 1 10 9.0 3.0
D 3 2 5 11 8.0 4.0
A 0 4 0 12 12.0 3.0
C 2 4 3 16 14.0 3.5
E 4 4 7 17 13.0 3.25
平均周转时间为:11.2 平均带权周转时间为:3.35
参考文献
CSDN - Jiong-952 【JAVA操作系统实验——进程调度】时间片轮转法
博客园 - 照照镜子吧 进程调度 RR 时间片轮转调度 java python实现
bilibili - bili_95149190474 RR时间片轮转调度算法 ~相同到达时间