Java线程常用调度算法与应用

简介: Java线程常用调度算法与应用

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,优先级高的先得到执行,而与服务时间,加入的时间无关

目录
相关文章
|
16天前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
43 15
|
23天前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
15天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
66 23
|
11天前
|
人工智能 自然语言处理 供应链
从第十批算法备案通过名单中分析算法的属地占比、行业及应用情况
2025年3月12日,国家网信办公布第十批深度合成算法通过名单,共395款。主要分布在广东、北京、上海、浙江等地,占比超80%,涵盖智能对话、图像生成、文本生成等多行业。典型应用包括医疗、教育、金融等领域,如觅健医疗内容生成算法、匠邦AI智能生成合成算法等。服务角色以面向用户为主,技术趋势为多模态融合与垂直领域专业化。
|
7天前
|
算法 数据可视化 调度
基于NSGAII的的柔性作业调度优化算法MATLAB仿真,仿真输出甘特图
本程序基于NSGA-II算法实现柔性作业调度优化,适用于多目标优化场景(如最小化完工时间、延期、机器负载及能耗)。核心代码完成任务分配与甘特图绘制,支持MATLAB 2022A运行。算法通过初始化种群、遗传操作和选择策略迭代优化调度方案,最终输出包含完工时间、延期、机器负载和能耗等关键指标的可视化结果,为制造业生产计划提供科学依据。
|
9天前
|
JavaScript 前端开发 算法
JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
Array.sort() 是一个功能强大的方法,通过自定义的比较函数,可以处理各种复杂的排序逻辑。无论是简单的数字排序,还是多字段、嵌套对象、分组排序等高级应用,Array.sort() 都能胜任。同时,通过性能优化技巧(如映射排序)和结合其他数组方法(如 reduce),Array.sort() 可以用来实现高效的数据处理逻辑。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
18天前
|
存储 人工智能 算法
通过Milvus内置Sparse-BM25算法进行全文检索并将混合检索应用于RAG系统
阿里云向量检索服务Milvus 2.5版本在全文检索、关键词匹配以及混合检索(Hybrid Search)方面实现了显著的增强,在多模态检索、RAG等多场景中检索结果能够兼顾召回率与精确性。本文将详细介绍如何利用 Milvus 2.5 版本实现这些功能,并阐述其在RAG 应用的 Retrieve 阶段的最佳实践。
通过Milvus内置Sparse-BM25算法进行全文检索并将混合检索应用于RAG系统
|
24天前
|
存储 缓存 监控
企业监控软件中 Go 语言哈希表算法的应用研究与分析
在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
33 3
|
22天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
92 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
12天前
|
人工智能 自然语言处理 算法
从第九批深度合成备案通过公示名单分析算法备案属地、行业及应用领域占比
2024年12月20日,中央网信办公布第九批深度合成算法名单。分析显示,教育、智能对话、医疗健康和图像生成为核心应用领域。文本生成占比最高(57.56%),涵盖智能客服、法律咨询等;图像/视频生成次之(27.32%),应用于广告设计、影视制作等。北京、广东、浙江等地技术集中度高,多模态融合成未来重点。垂直行业如医疗、教育、金融加速引入AI,提升效率与用户体验。