Linux操作系统课程设计,使用JAVA实现时间片轮转法

Linux操作系统课程设计 - 使用JAVA实现时间片轮转法

题目

实验2:时间片轮转法

基本思想:将CPU 的处理时间划分成一个个时间片,就绪队列中的诸进程轮流运行一个时间片,当时间片结束时,就强迫运行进程让出CPU,该进程进入就绪队列,等待下一次调度,同时,进程调度又去选择就绪队列中的一个进程,分配给它一个时间片,以投入运行。在轮转法中,时间片长度的选择非常重要,将直接影响系统开销和响应时间。如果时间片长度很小,则调度程序剥夺处理机的次数频繁,加重系统开销;反之,如果时间片长度选择过长,比方说一个时间片就能保证就绪队列中所有进程都执行完毕,则轮转法就退化成先进先出算法。

结论可以归结如下:时间片设得太短会导致过多的进程切换,降低了CPU效率;而设得太长又可能引起对短的交互请求的响应变差。将时间片设为100毫秒通常是一个比较合理的折衷。

注意事项

题目中提到的是100毫秒,本文采用的是书中的一组数据,是以一个时间单位计算

作业情况进程名ABCDE平均值
到达时间01234
服务时间43424
q=1完成时间1210161117
周转时间1291481311.2
带权周转时间333.543.253.35
q=4完成时间47111317
周转时间46910138.4
带权周转时间122.2553.252.7
q=1 and q=4
A 0 4
B 1 3
C 2 4
D 3 2
E 4 4

图片来自https://www.bilibili.com/video/BV1hB4y1A7gy

为了符合题目的要求建议使用如下数据

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时间片轮转调度算法 ~相同到达时间

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值