调度算法的介绍及代码实现

1,043 阅读4分钟

这是我参与8月更文挑战的第29天,活动详情查看: 8月更文挑战

在生活中,我们经常都会听到列车调度、调度员这样的名词,在编程中,与我们接触最多的都是操作系统,因为操作系统资源的有限性,操作系统上面运行的进程又比较多,这个时候就会用到调度,用有限的资源做最多的事情。在调度中,存在几种比较常用的算法:先来先服务(FCFS)、短作业优先(SJF)、时间片轮转(RR)、优先级调度(HPF),下面一一来看下这几种调度算法。

先来先服务(FCFS)

  • 概述

先来先服务,就是讲究先来后到,像我们去餐厅打饭一样,先来的先打饭,后来的人则排队 这种算法如果遇到一个任务阻塞需要等待其他资源后才可以执行后续操作的话,这样的话就会一直阻塞在该任务,其他后续的任务则需要排队等待很久的时间,这样的话也会浪费资源一直挂起等待,该算法只适用于处理时间比较均衡的业务,这样的话处理起来不会浪费太多的时间。

  • 算法实现介绍

可以采用队列(BlockingQueue)来实现该算法,队列头部按照先放入的顺序取出来,然后执行,需要执行的任务从尾部放入

  • 简易代码
public class FCFS {
    public static void main(String[] args) throws InterruptedException {
        final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue(5);
        //模拟执行任务
        new Thread(() -> {
            while (true) {
                try {
                    queue.take().run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        //模拟存放任务
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            queue.put(() -> {
                System.out.println(finalI);
            });
        }
    }
}

短作业优先(SJF)

  • 概述

每次调度的时候从需要调度的池中,选取一个执行时间比较短的任务来执行,每次调度执行的任务都是执行时间比较短的任务来执行,由于执行时间是基于自己的预估告知调度器的,可能无法做到很精准。 这种算法适用于任务时间差别比较大的场景,执行时间比较大的就往后面靠,等执行时间短的执行完成后在执行,但是该算法针对优先级比较高,执行时间又比较长的任务不是那么的友好,对于长作业的任务,可能一直都被短作业任务占据而无法得到执行。

  • 算法实现介绍

可以采用TreeMap来实现该算法,map根据key值自动排序,每次获取最小的一个key来执行任务。

  • 简易代码
public class SJF {

    public static void main(String[] args) {
        final TreeMap<Integer, Runnable> map = new TreeMap<>();
        //模拟执行任务
        new Thread(() -> {
            while (true) {
                Map.Entry<Integer, Runnable> entry = map.pollFirstEntry();
                if (entry != null) {
                    System.out.println("当前key:" + entry.getKey());
                    entry.getValue().run();
                }
            }
        }).start();
        //模拟存放任务
        for (int i = 0; i < 5; i++) {
            map.put(new Random().nextInt(1000), () -> {
                System.out.println("执行时间:"+new Random().nextInt(10));
            });
        }
    }
}

执行结果:

image.png

从结果可以看出,每次执行的时候都从map取最小的key来执行,对于该任务的实际执行时间并不关心。

时间片轮转(RR)

  • 概述

时间片轮转是比较古老的,最简单,并且使用的最广的一种算法,操作系统都采用的是这样的算法。对于要执行的任务,都被分配一段时间片,当时间片用完后,该任务还没有执行完成则暂停该任务执行,将时间片分配给其他的任务执行,等到该任务获取到时间片后,才继续执行该任务。 因为在执行的时候,进程会不停的切换,在进程切换的过程中也会造成资源的浪费,所以在编程中我们有时候采用多线程执行任务并不一定比单线程执行快,因为CPU切换也会造成一定的浪费。

  • 算法实现介绍

该算法没有想到好的实现

优先级调度(HPF)

  • 概述

优先级调度就是按照优先级的高低来决定执行的顺序,在每次调度任务的时候,根据需要执行任务的优先级来决定任务的执行顺序,每次都取优先级比较高的任务来执行,就像生活中VIP和VVIP的概念一样,优先级高就先执行。

  • 算法实现介绍

也可以采用TreeMap来实现该算法,只不过key存优先级,每次执行任务的时候从中取key比较大的任务来执行

  • 简易代码
public class HPF {

    public static void main(String[] args) {
        final TreeMap<Integer, Runnable> map = new TreeMap<>();
        //模拟执行任务
        new Thread(() -> {
            while (true) {
                Map.Entry<Integer, Runnable> entry = map.pollLastEntry();
                if (entry != null) {
                    System.out.println("当前key:" + entry.getKey());
                    entry.getValue().run();
                }
            }
        }).start();
        //模拟存放任务
        for (int i = 1; i<= 5; i++) {
            map.put(i, () -> System.out.println("执行时间:" + new Random().nextInt(1000)));
        }
    }
}

执行结果

image.png

第一个执行的key=2,那是可能在任务执行的时候,后面的数据还没有存入,所以就先把1的任务拿来执行,当第二次执行的时候,里面存放了后多的任务,就按照key的优先级来从大到小执行任务