数据结构和算法专题---6、定时算法与应用

本章我们会对定时算法做个简单介绍,包括常用的定时算法(最小堆、时间轮)的概述、实现方式、典型场景做个说明。

概述

系统或者项目中难免会遇到各种需要自动去执行的任务,实现这些任务的手段也多种多样,如操作系统的crontab,spring框架的quartz,java的Timer和ScheduledThreadPool都是定时任务中的典型手段。

最小堆

概念

Timer是java中最典型的基于优先级队列+最小堆实现的定时器,内部维护一个存放定时任务的优先级队列,该优先级队列使用了最小堆排序。当我们调用schedule方法的时候,一个新的任务被加入queue,堆重排,始终保持堆顶是执行时间最小(即最近马上要执行)的。同时,内部相当于起了一个线程不断扫描队列,从队列中依次获取堆顶元素执行,任务得到调度。

下面以Timer为例,介绍优先级队列+最小堆算法的实现原理:

案例

package com.ls.cloud.sys.alg.Timer;import java.util.Timer;
import java.util.TimerTask;class Task extends TimerTask {@Overridepublic void run() {System.out.println("running...");}
}
public class TimerDemo {public static void main(String[] args) {Timer t=new Timer();//在1秒后执行,以后每2秒跑一次t.schedule(new Task(), 1000,2000);}
}

源码分析

新加任务时,t.schedule方法会add到队列

 void add(TimerTask task) {// Grow backing store if necessaryif (size + 1 == queue.length)queue = Arrays.copyOf(queue, 2*queue.length);queue[++size] = task;fixUp(size);}

add实现了容量维护,不足时扩容,同时将新任务追加到队列队尾,触发堆排序,始终保持堆顶元素最小

 private void fixUp(int k) {while (k > 1) {//k指针指向当前新加入的节点,也就是队列的末尾节点,j为其父节点int j = k >> 1;//如果新加入的执行时间比父节点晚,那不需要动if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)break;//如果大于其父节点,父子交换TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;//交换后,当前指针继续指向新加入的节点,继续循环,知道堆重排合格k = j;}}

线程调度中的run,主要调用内部mainLoop()方法,使用while循环

  private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized(queue) {// Wait for queue to become non-emptywhile (queue.isEmpty() && newTasksMayBeScheduled)queue.wait();if (queue.isEmpty())break; // Queue is empty and will forever remain; die// Queue nonempty; look at first evt and do the right thinglong currentTime, executionTime;task = queue.getMin();synchronized(task.lock) {if (task.state == TimerTask.CANCELLED) {queue.removeMin();continue;  // No action required, poll queue again}currentTime = System.currentTimeMillis();executionTime = task.nextExecutionTime;//判断是否到了执行时间if (taskFired = (executionTime<=currentTime)) {//判断下一次执行时间,单次的执行完移除                        //循环的修改下次执行时间if (task.period == 0) { // Non-repeating, removequeue.removeMin();task.state = TimerTask.EXECUTED;} else { // Repeating task, reschedule//下次时间的计算有两种策略                            //1.period是负数,那下一次的执行时间就是当前时间‐period                            //2.period是正数,那下一次就是该任务本次的执行时间+period                            //注意!这两种策略大不相同。因为Timer是单线程的                            //如果是1,那么currentTime是当前时间,就受任务执行长短影响                            //如果是2,那么executionTime是绝对时间戳,与任务长短无关queue.rescheduleMin(task.period<0 ? currentTime   - task.period: executionTime + task.period);}}}//不到执行时间,等待if (!taskFired) // Task hasn't yet fired; waitqueue.wait(executionTime - currentTime);}//到达执行时间,run!if (taskFired)  // Task fired; run it, holding no lockstask.run();} catch(InterruptedException e) {}}}
}

应用

  • Timer是单线程,一旦一个失败或出现异常,将打断全部任务队列,线程池不会
  • Timer在jdk1.3+,而线程池需要jdk1.5+

时间轮

概念

时间轮是一种更为常见的定时调度算法,各种操作系统的定时任务调度,linux crontab,基于java的通信框架Netty等。其灵感来源于我们生活中的时钟。 轮盘实际上是一个头尾相接的环状数组,数组的个数即是插槽数,每个插槽中可以放置任务。 以1天为例,将任务的执行时间%12,根据得到的数值,放置在时间轮上,小时指针沿着轮盘扫描,扫到的点取出任务执行:

实现

package com.ls.cloud.sys.alg.Timer;public class RoundTask {//延迟多少秒后执行int delay;//加入的序列号,只是标记一下加入的顺序int index;public RoundTask(int index, int delay) {this.index = index;this.delay = delay;}void run() {System.out.println("task " + index + " start , delay = "+delay);}@Overridepublic String toString() {return String.valueOf(delay);}
}
package com.ls.cloud.sys.alg.Timer;import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class RoundDemo {//小轮槽数int size1=10;//大轮槽数int size2=5;//小轮,数组,每个元素是一个链表LinkedList<RoundTask>[] t1 = new LinkedList[size1];//大轮LinkedList<RoundTask>[] t2 = new LinkedList[size2];//小轮计数器,指针跳动的格数,每秒加1final AtomicInteger flag1=new AtomicInteger(0);//大轮计数器,指针跳动个格数,即每10s加1final AtomicInteger flag2=new AtomicInteger(0);//调度器,拖动指针跳动ScheduledExecutorService service = Executors.newScheduledThreadPool(2);public RoundDemo(){//初始化时间轮for (int i = 0; i < size1; i++) {t1[i]=new LinkedList<>();}for (int i = 0; i < size2; i++) {t2[i]=new LinkedList<>();}}//打印时间轮的结构,数组+链表void print(){System.out.println("t1:");for (int i = 0; i < t1.length; i++) {System.out.println(t1[i]);}System.out.println("t2:");for (int i = 0; i < t2.length; i++) {System.out.println(t2[i]);}}//添加任务到时间轮void add(RoundTask task){int delay = task.delay;if (delay < size1){//10以内的,在小轮,槽取余数t1[delay%size1].addLast(task);}else {//超过小轮的放入大轮,槽除以小轮的长度t2[delay/size1].addLast(task);}}void startT1(){//每秒执行一次,推动时间轮旋转,取到任务立马执行service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {int point = flag1.getAndIncrement()%size1;System.out.println("t1 -----> slot "+point);LinkedList<RoundTask> list = t1[point];if (!list.isEmpty()){//如果当前槽内有任务,取出来,依次执行,执行完移除while (list.size() != 0){list.getFirst().run();list.removeFirst();}}}},0,1, TimeUnit.SECONDS);}void startT2(){//每10秒执行一次,推动时间轮旋转,取到任务下方到t1service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {int point = flag2.getAndIncrement()%size2;System.out.println("t2 =====> slot "+point);LinkedList<RoundTask> list = t2[point];if (!list.isEmpty()){//如果当前槽内有任务,取出,放到定义的小轮while (list.size() != 0){RoundTask task = list.getFirst();//放入小轮哪个槽呢?小轮的槽按10取余数t1[task.delay % size1].addLast(task);//从大轮中移除list.removeFirst();}}}},0,10, TimeUnit.SECONDS);}public static void main(String[] args) {RoundDemo roundDemo = new RoundDemo();//生成100个任务,每个任务的延迟时间随机for (int i = 0; i < 100; i++) {roundDemo.add(new RoundTask(i,new Random().nextInt(50)));}//打印,查看时间轮任务布局roundDemo.print();//启动大轮roundDemo.startT2();//小轮启动roundDemo.startT1();}}

结果

t1 -----> slot 1
task 8 start , delay = 11
task 45 start , delay = 11
task 87 start , delay = 11
t1 -----> slot 2
task 40 start , delay = 12
task 89 start , delay = 12
t1 -----> slot 3
task 25 start , delay = 13
t1 -----> slot 4
task 69 start , delay = 14
t1 -----> slot 5
  • 输出结果严格按delay顺序执行,而不管index是何时被提交的
  • t1为小轮,10个槽,每个1s,10s一轮回
  • t2为大轮,5个槽,每个10s,50s一轮回
  • t1循环到每个槽时,打印槽内的任务数据,如 t1–>slot9 , 打印了3个9s执行的数据
  • t2循环到每个槽时,将槽内的任务delay时间取余10后,放入对应的t1槽中,如 t2==>slot1
  • 那么t1旋转对应的圈数后,可以取到t2下放过来的任务并执行,如10,11…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/211897.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】使用“/**/“进行注释的好处

2023年12月10日&#xff0c;周日晚上 我今天下午看Google Chrome的源码时&#xff0c;才发现"/**/"原来还能这么用 使用"/**/"的好处就是&#xff0c;可以在任何地方进行注释&#xff0c;哪怕是参数列表 void CircularWindow::enterEvent(QEvent *event/…

【Python】判断域名是否合法

python判断域名是否合法|校验域名 域名以点号分隔成多个字符串。单个字符串由各国文字的特定字符集、字母、数字、连字符&#xff08;-&#xff09;组成&#xff0c;字母不区分大小写&#xff0c;连字符&#xff08;-&#xff09;不得出现在字符串的头部或者尾部。单个字符串长…

GitHub Enterprise Server 添加代码安全、自动化功能

GitHub的软件更新用于管理私有服务器上的存储库&#xff0c;具有GitHub容器注册访问、Dependabot安全警报和更新以及可重用工作流的特性。 GitHub Enterprise Server 3.5是GitHub用于托管和管理私有服务器上存储库的最新版本&#xff0c;它引入了新的代码安全特性&#xff0c;新…

Helm 常用运维命令

原理参考 ## https://blog.csdn.net/knight_zhou/article/details/122079292 常用运维命令 helm search:   搜索charthelm pull:    下载chart到本地目录查看helm install:   上传chart到Kuberneteshelm list:     列出已发布的chart

【开源】基于Vue和SpringBoot的车险自助理赔系统

项目编号&#xff1a; S 018 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S018&#xff0c;文末获取源码。} 项目编号&#xff1a;S018&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 车…

Maven基础

目录 Maven坐标 坐标简介 主要组成 Maven依赖管理 配置依赖 依赖简介 配置依赖 依赖传递 依赖传递简介 排除依赖 依赖范围 生命周期 生命周期简介 执行指定生命周期 Maven坐标 坐标简介 Maven中的坐标是资源的唯一标识&#xff0c;通过该坐标可以唯一定位资…

Redis交互速度慢,CPU占用100%,集群方案,报错等问题

后续补充结论 仔细查看前辈们堆的代码中发现居然调用了大量key*查询&#xff0c;导致走的遍历非常慢&#xff01;因为这相当与全部数据量遍历&#xff0c;即这个原因导致了查询速度与数据量成正比&#xff0c;推测也是CPU占用高的元凶&#xff1b;即使加上key前缀再匹配*也会走…

Python开发运维:Python调用K8S API实现资源管理

目录 一、实验 1.Python操作K8S API获取资源 2.Python操作K8S API创建deployment资源 3.Python操作K8S API删除k8s资源 4.Python操作K8S API修改k8s资源 5.Python操作K8S API查看k8s资源 二、问题 1.Windows11安装kubernetes报错 2.Python通过调用哪些方法实现Pod和De…

在SpringData JPA 中实现对持久层的操作

1.导入依赖 hibernate 这个依赖自带实现JPA接口 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><depen…

TCP三次握手、四次挥手及状态转换详解

1.什么是TCP协议&#xff1f; 传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff0c;位于网络OSI七层模型的第四层&#xff0c;IP协议一起工作&#xff0c;TCP层是位于IP层之上…

(Spring学习07)Spring之启动刷新过程源码解析

概述 通常&#xff0c;我们说的Spring启动&#xff0c;就是构造ApplicationContext对象以及调用refresh()方法的过程。 首先&#xff0c;Spring启动过程主要做了这么几件事情&#xff1a; 构造一个BeanFactory对象解析配置类&#xff0c;得到BeanDefinition&#xff0c;并注册…

CrystalDiskInfo中文版(硬盘检测工具) v9.1.1.0 绿色汉化版-供大家学习研究参考

更新内容 重新支持三星SATA SSD寿命报告 增加对ZHITAI SC001的支持 新增SK hynix Gold S31支持 增加了KLEVV NEO N610的支持。 改进的Micron/Crucial SATA SSD支持 已更改 卸载程序将显示一个确认对话框&#xff0c;用于删除设置。 强大功能 1.拥有多国语言&#xff0c;…

27 动态规划解最大子序和

问题描述&#xff1a;给定一个整数数组nums&#xff0c;找到一个具有最大和的连续子数组(子数组最少含有一个元素)&#xff0c;返回其最大和。 动态规划求解&#xff1a;定义dp[i]表示以i元素为结尾的最大和&#xff0c;如果dp[i-1]小于零的话&#xff0c;dp[i]nums[i],否则dp…

React-hook-form-mui(三):表单验证

前言 在上一篇文章中&#xff0c;我们介绍了react-hook-form-mui的基础用法。本文将着重讲解表单验证功能。 react-hook-form-mui提供了丰富的表单验证功能&#xff0c;可以通过validation属性来设置表单验证规则。本文将详细介绍validation的三种实现方法&#xff0c;以及如何…

ts中type和interface类型声明的区别

1. 写法上 type 使用关键字 type 进行声明。 interface 使用关键字 interface 进行声明。 // 使用 type type MyType {param: string; };// 使用 interface interface MyInterface {param: string; }2. 可合并性 interface 具有可合并性&#xff0c;允许在同一作用域内多次…

045:Vue读取本地上传JSON文件,导出JSON文件方法

第045个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

jquery手写广告轮播图,无限循环功能

说明 在很多情况下&#xff0c;我们都需要开发广告轮播图&#xff0c;当我们进行页面的功能开发时&#xff0c;采用轮播图来实现也行&#xff0c;但是很多情况下&#xff0c;我们只需要简单的控制轮播循环轮播展示即可&#xff0c;所以用jq开开发广告轮播波&#xff0c;自定义…

spring更加松散的获取bean的方式ObjectProvider

概述 ObjectProvider直译就是对象提供者; 平时从spring中获取bean都是调用beanFactory.getBean()方法&#xff0c;如果bean不存在则会直接抛异常; 从spring 4.3开始引入了org.springframework.beans.factory.ObjectProvider接口,其提供了若干的方法&#xff0c;可以更松散的…

Idea 插件开发: Swing Designer设计器创建的组件全部为空问题记录

问题现象 通过Swing 设计器创建的对象, Swing组件全部是空的, 导致ToolWindowFactory工厂的实现类调用时候出现了空指针异常 如下方式创建的 问题分析 问题出现时候, 同时给我生成了一个createUIComponents的私有方法, 由于个人当时理解有误, 把他当成了初始化方法, 在里面…

Oracle高可用一家老小全在这里

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…