1 调度算法应用
调度算法常见于操作系统中,因为系统资源有限,当有多个进程(或多个进程发出的请求)要使用这些资源时,就必须按照一定的原则选择进程(请求)来占用资源。这就是所谓的调度。在现实生活中也是一样,比如会议室的占用。
CPU资源调度
云计算资源调度
容器化Docker编排与调度
2 先来先服务(FCFS)
2.1 概念
先来先服务,很好理解,就是按照服务提交申请的顺序,依次执行。讲究先来后到。
2.2 实现
定义一个Task类作为任务实例,BlockingQueue作为服务队列
package com.oldlu.schedule;
package com.oldlu.schedule; /** * 任务类 */ public class Task { //任务名称 private String name; //任务提交的时间 private Long addTime; //任务的执行时间长短 private int servTime; public Task(String name, int servTime) { this.name = name; this.servTime = servTime; this.addTime = System.currentTimeMillis(); } public void execute() { try { // !重点:执行时睡眠,表示该任务耗时servTime毫秒 Thread.currentThread().sleep(servTime); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(String.format("execute:name=%s,addTime=%s,servTime=%s", name, addTime, servTime)); } }
package com.oldlu.schedule; import java.util.Random; import java.util.concurrent.LinkedBlockingQueue; public class FCFS { public static void main(String[] args) throws InterruptedException { //阻塞队列,FCFS的基础 final LinkedBlockingQueue<Task> queue = new LinkedBlockingQueue(5); //服务线程,任务由该线程获取和执行 new Thread(new Runnable(){ @Override public void run() { while (true) { try { queue.take().execute(); } catch (Exception e) { e.printStackTrace(); } } } }).start(); //向队列中放入一个任务 for (int i = 0; i < 5; i++) { System.out.println("add task:"+i); queue.put(new Task("task"+i,new Random().nextInt(1000))); } } }
3)结果分析
add按顺序放入,时间有序
execute也按时间顺序执行,而不管后面的servTime,也就是不管执行任务长短,先来先执行
2.4 优缺点
多应用于cpu密集型任务场景,对io密集型的不利。
时间相对均衡的业务可以排队处理,比如现实中排队打卡进站。
如果业务需要依赖大量的外部因素,执行时间片长短不一,FCFS算法不利于任务的整体处理进度,可能会因
为一个长时间业务的阻塞而造成大量等待。
3 短作业优先 (SJF)
3.1 概念
执行时间短的优先得到资源。即执行前申报一个我需要占据cpu的时间,根据时间长短,短的优先被调度。我不占
时间所以我先来。
3.2 实现
使用TreeMap可以实现优先级的任务排序。
package com.oldlu.schedule; import javax.swing.text.html.parser.Entity; import java.util.Map; import java.util.Random; import java.util.TreeMap; public class SJF { public static void main(String[] args) throws InterruptedException { //有序Map,将服务时间作为key排序 final TreeMap<Integer,Task> treeMap = new TreeMap(); //向队列中放入5个任务 for (int i = 0; i < 5; i++) { System.out.println("add task:"+i); int servTime = new Random().nextInt(1000); //注意,key是servTime,即执行预估时间 treeMap.put(servTime,new Task("task"+i,servTime)); } //服务线程,任务由该线程获取和执行 new Thread(new Runnable(){ @Override public void run() { while (true) { try { //有序Map中,服务时间短的,置于顶部,那么自然就会优先被取出 Map.Entry<Integer,Task> entry = treeMap.pollFirstEntry(); if (entry == null){ Thread.currentThread().sleep(100); }else { entry.getValue().execute(); } } catch (Exception e) { e.printStackTrace(); } } } }).start(); } }
3.3 结果分析
add任务有序,确实按照从前往后顺序提交的
execute任务无序,按servtime排序,说明执行时间段的得到了优先执行
3.4 优缺点
适用于任务时间差别较大的场景,仍然以进站为例,拿出公交卡的优先刷卡,还没办卡的让一让。
解决了FCFS整体处理时间长的问题,降低平均等待时间,提高了系统吞吐量。
未考虑作业的紧迫程度,因而不能保证紧迫性作业(进程)的及时处理
对长作业的不利,可能等待很久而得不到执行
时间基于预估和申报,主观性因素在内,无法做到100%的精准
4 时间片轮转(RR)
4.1 概念
时间片逐个扫描轮询,轮到谁谁执行。大家公平裁决来者有份,谁也别插队。像是棋牌游戏中的发牌操作,做到了
时间和机会上的平均性。
4.2 实现
基于数组做为数据插槽方式实现。
package com.oldlu.schedule; import java.util.Random; public class RR { //定义数组作为插槽,每个插槽中可以放入任务 Integer[] integers; //length插槽的个数 public RR(int length){ integers = new Integer[length]; } //将任务放入插槽 public void addTask(int value){ int slot = 0; //不停查找空的插槽 while (true) { //发现空位,将当前任务放入 if (integers[slot] == null){ integers[slot] = value; System.out.println(String.format("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>add task index=%s,value=%s",slot,value)); break; } //如果当前位置有任务占用,看下一个位置 slot++; //如果插槽遍历完还是没有空位置,那么从头开始再找,继续下一个轮回 if (slot == integers.length){ slot = 0; } } } //执行任务。轮询的策略就在这里 public void execute(){ //开启一个线程处理任务。在现实中可能有多个消费者来处理 new Thread(new Runnable() { @Override public void run() { int index = 0; while (true) { //指针轮询,如果到达尾部,下一步重新转向开头 // 数据物理结构是一个数组,逻辑上是一个环 if (index == integers.length){ index = 0; } //如果当前位置没有任务,轮询到下一个插槽 if (integers[index] == null){ index++; continue; }else{ //随机等待,表示模拟当前任务有一个执行时间 try { Thread.currentThread().sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } //模拟任务执行的内容,也就是打印一下当前插槽和里面的值 System.out.println(String.format("execute index=%s,value=%s",index,integers[index])); //执行完,将当前插槽清空,腾出位置来给后续任务使用 integers[index] = null; } } } }).start(); } public static void main(String[] args) { //测试开始,定义3个插槽 RR rr = new RR(3); //唤起执行者线程,开始轮询 rr.execute(); //放置10个任务 for (int i = 0; i < 10; i++) { rr.addTask(i); } } }
4.3 结果分析
add任务index无序,value有序,说明是按顺序提交的,但是插槽无序,哪里有空放哪里
execute执行index有序value无序,说明任务是轮询执行的,每个插槽里的任务不一定是谁
4.4 优缺点
做到了机会的相对平均,不会因为某个任务执行时间超长而永远得不到执行
缺乏任务主次的处理。重要的任务无法得到优先执行,必须等到时间片轮到自己,着急也没用
5 优先级调度(HPF)
5.1 概述
进程调度每次将处理机分配给具有最高优先级的就绪进程。最高优先级算法可与不同的CPU方式结合形成可抢占式
最高优先级算法和不可抢占式最高优先级算法。
5.2 实现
在Task类中新增一个属性level作为优先级标识
package com.oldlu.schedule; /** * 任务类 */ public class Task { //任务名称 private String name; //任务提交的时间 private Long addTime; //任务的执行时间 private int servTime; //任务优先级 private int level; public Task(String name, int servTime) { this.name = name; this.servTime = servTime; this.addTime = System.currentTimeMillis(); } public Task(String name, int servTime,int level) { this.name = name; this.servTime = servTime; this.level = level; this.addTime = System.currentTimeMillis(); } public void execute() { try { // !重点:执行时睡眠,表示该任务耗时servTime毫秒 Thread.currentThread().sleep(servTime); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( String.format("execute:name=%s,level=%s,addTime=%s,servTime=%s", name,level, addTime, servTime)); } }
依然使用TreeMap实现排序,注意的是,key要取优先级
package com.oldlu.schedule; import java.util.Map; import java.util.Random; import java.util.TreeMap; public class HPF { public static void main(String[] args) throws InterruptedException { //有序Map,将服务优先级作为key排序 final TreeMap<Integer, Task> treeMap = new TreeMap(); //向队列中放入5个任务 for (int i = 0; i < 5; i++) { System.out.println("add task:" + i); int servTime = new Random().nextInt(1000); //注意放入的key,是优先级,这里用i标识 treeMap.put(i, new Task("task" + i, servTime,i)); } //服务线程,任务由该线程获取和执行 new Thread(new Runnable() { @Override public void run() { while (true) { try { //有序Map中,优先级最高的,在底部部,那么自然就会优先被取出 Map.Entry<Integer, Task> entry = treeMap.pollLastEntry(); if (entry == null) { Thread.currentThread().sleep(100); } else { entry.getValue().execute(); } } catch (Exception e) { e.printStackTrace(); } } } }).start(); } }
按0-4顺序加入task
执行时,按4-0,优先级高的先得到执行,而与服务时间,加入的时间无关