定时任务详解

1、定时任务

在公司做项目时,经常遇到要用到定时任务的事情,但是对定时任务不熟练的时候会出现重复任务的情况,不深入,这次将定时任务好好学习分析一下

定时任务的原理

假如我将一个现场通过不断轮询的方式去判断,就能实现定时任务功能

public class Task {public static void main(String[] args) {// run in a secondfinal long timeInterval = 1000;Runnable runnable = new Runnable() {@Overridepublic void run() {while (true) {System.out.println("Hello !!");try {Thread.sleep(timeInterval);} catch (InterruptedException e) {e.printStackTrace();}}}};Thread thread = new Thread(runnable);thread.start();}
}

这里解释下InterruptedException异常

 
Thrown when a thread is waiting, sleeping, or otherwise occupied, and the thread is interrupted,either before or during the activity. Occasionally a method may wish to test whether the currentthread has been interrupted, and if so, to immediately throw this exception.The following code can be used to achieve this effect:

简单来说就是当阻塞方法收到中断请求的时候就会抛出InterruptedException异常,当一个方法后面声明可能会抛出InterruptedException 异常时,说明该方法是可能会花一点时间,但是可以取消的方法。

抛InterruptedException的代表方法有:sleep(),wait(),join()
执行wait方法的线程,会进入等待区等待被notify/notify All。在等待期间,线程不会活动。

执行sleep方法的线程,会暂停执行参数内所设置的时间。

执行join方法的线程,会等待到指定的线程结束为止。

因此,上面的方法都是需要花点时间的方法。这三个方法在执行过程中会不断轮询中断状态(interrupted方法),从而自己抛出InterruptedException。

interrupt方法其实只是改变了中断状态而已。

所以,如果在线程进行其他处理时,调用了它的interrupt方法,线程也不会抛出InterruptedException的,只有当线程走到了sleep, wait, join这些方法的时候,才会抛出InterruptedException。若是没有调用sleep, wait, join这些方法,或者没有在线程里自己检查中断状态,自己抛出InterruptedException,那InterruptedException是不会抛出来的。

Timer实现

Java在1.3版本引入了Timer工具类,它是一个古老的定时器,搭配TimerTask和TaskQueue一起使用,示例

public class TimeTaskTest {public static void main(String[] args) {TimerTask timerTask = new TimerTask() {@Overridepublic void run() {System.out.println("hell world");}};Timer timer = new Timer();timer.schedule(timerTask, 10, 3000);}
}
//Timer类
public void schedule(TimerTask task, long delay, long period) {if (delay < 0)throw new IllegalArgumentException("Negative delay.");if (period <= 0)throw new IllegalArgumentException("Non-positive period.");sched(task, System.currentTimeMillis()+delay, -period);}
private void sched(TimerTask task, long time, long period) {if (time < 0)throw new IllegalArgumentException("Illegal execution time.");// Constrain value of period sufficiently to prevent numeric// overflow while still being effectively infinitely large.if (Math.abs(period) > (Long.MAX_VALUE >> 1))period >>= 1;synchronized(queue) {if (!thread.newTasksMayBeScheduled)throw new IllegalStateException("Timer already cancelled.");synchronized(task.lock) {if (task.state != TimerTask.VIRGIN)throw new IllegalStateException("Task already scheduled or cancelled");task.nextExecutionTime = time;task.period = period;task.state = TimerTask.SCHEDULED;}queue.add(task);if (queue.getMin() == task)queue.notify();}}

imer中用到的主要是两个成员变量:

1、TaskQueue:一个按照时间优先排序的队列,这里的时间是每个定时任务下一次执行的毫秒数(相对于1970年1月1日而言)

2、TimerThread:对TaskQueue里面的定时任务进行编排和触发执行,它是一个内部无限循环的线程。

主要方法:

// 在指定延迟时间后执行指定的任务(只执行一次)
schedule(TimerTask task,long delay);// 在指定时间执行指定的任务。(只执行一次)
schedule(TimerTask task, Date time);// 延迟指定时间(delay)之后,开始以指定的间隔(period)重复执行指定的任务
schedule(TimerTask task,long delay,long period);// 在指定的时间开始按照指定的间隔(period)重复执行指定的任务
schedule(TimerTask task, Date firstTime , long period);// 在指定的时间开始进行重复的固定速率执行任务
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);// 在指定的延迟后开始进行重复的固定速率执行任务
scheduleAtFixedRate(TimerTask task,long delay,long period);// 终止此计时器,丢弃所有当前已安排的任务。
cancal();// 从此计时器的任务队列中移除所有已取消的任务。
purge();

先说Fixed Delay模式

//从当前时间开始delay个毫秒数开始定期执行,周期是period个毫秒数
public void schedule(TimerTask task, long delay, long period) {...}
从指定的firstTime开始定期执行,往后每次执行的周期是period个毫秒数
public void schedule(TimerTask task, Date firstTime, long period){...}

它的工作方式是:

第一次执行的时间将按照指定的时间点执行(如果此时TimerThread不在执行其他任务),如有其他任务在执行,那就需要等到其他任务执行完成才能执行。

从第二次开始,每次任务的执行时间是上一次任务开始执行的时间加上指定的period毫秒数。

如何理解呢,我们还是看代码

public static void main(String[] args) {TimerTask task1 = new DemoTimerTask("Task1");TimerTask task2 = new DemoTimerTask("Task2");Timer timer = new Timer();timer.schedule(task1, 1000, 5000);timer.schedule(task2, 1000, 5000);
}static class DemoTimerTask extends TimerTask {private String taskName;private DateFormat df = new SimpleDateFormat("HH:mm:ss---");public DemoTimerTask(String taskName) {this.taskName = taskName;}@Overridepublic void run() {System.out.println(df.format(new Date()) + taskName + " is working.");try {Thread.sleep(2000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(df.format(new Date()) + taskName + " finished work.");}
}

task1和task2是几乎同时执行的两个任务,而且执行时长都是2秒钟,如果此时我们把第六行注掉不执行,我们将得到如下结果(和第三种Fixed Rate模式结果相同):

13:42:58---Task1 is working.
13:43:00---Task1 finished work.
13:43:03---Task1 is working.
13:43:05---Task1 finished work.
13:43:08---Task1 is working.
13:43:10---Task1 finished work.

如果打开第六行,我们再看下两个任务的执行情况。我们是期望两个任务能够同时执行,但是Task2是在Task1执行完成后才开始执行(原因是TimerThread是单线程的,每个定时任务的执行也在该线程内完成,当多个任务同时需要执行时,只能是阻塞了),从而导致Task2第二次执行的时间是它上一次执行的时间(13:43:57)加上5秒钟(13:44:02)。

13:43:55---Task1 is working.
13:43:57---Task1 finished work.
13:43:57---Task2 is working.
13:43:59---Task2 finished work.
13:44:00---Task1 is working.
13:44:02---Task1 finished work.
13:44:02---Task2 is working.
13:44:04---Task2 finished work.

那如果此时还有个Task3也是同样的时间点和间隔执行会怎么样呢?

结论是:也将依次排队,执行的时间依赖两个因素:

1.上次执行的时间

2.期望执行的时间点上有没有其他任务在执行,有则只能排队了

Fixed Rate模式

public static void main(String[] args) {TimerTask task1 = new DemoTimerTask("Task1");TimerTask task2 = new DemoTimerTask("Task2");Timer timer = new Timer();timer.scheduleAtFixedRate(task1, 1000, 5000);timer.scheduleAtFixedRate(task2, 1000, 5000);
}static class DemoTimerTask extends TimerTask {private String taskName;private DateFormat df = new SimpleDateFormat("HH:mm:ss---");public DemoTimerTask(String taskName) {this.taskName = taskName;}@Overridepublic void run() {System.out.println(df.format(new Date()) + taskName + " is working.");try {Thread.sleep(2000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(df.format(new Date()) + taskName + " finished work.");}
}

Task1和Task2还是在相同的时间点,按照相同的周期定时执行任务,我们期望Task1能够每5秒定时执行任务,期望的时间点是:14:21:47-14:21:52-14:21:57-14:22:02-14:22:07,实际上它能够交替着定期执行,原因是Task2也会定期执行,并且对TaskQueue的锁他们是交替着拿的(这个在下面分析TimerThread源码的时候会讲到)

14:21:47---Task1 is working.
14:21:49---Task1 finished work.
14:21:49---Task2 is working.
14:21:51---Task2 finished work.
14:21:52---Task2 is working.
14:21:54---Task2 finished work.
14:21:54---Task1 is working.
14:21:56---Task1 finished work.
14:21:57---Task1 is working.
14:21:59---Task1 finished work.
14:21:59---Task2 is working.
14:22:01---Task2 finished work.

TimerThread
上面我们主要讲了Timer的一些主要源码及定时模式,下面我们来分析下支撑Timer的定时任务线程TimerThread。

TimerThread大概流程图如下:

bcf64202309281148525995.png

源码如下

private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized(queue) {// 如果queue里面没有要执行的任务,则挂起TimerThread线程while (queue.isEmpty() && newTasksMayBeScheduled)queue.wait();// 如果TimerThread被激活,queue里面还是没有任务,则介绍该线程的无限循环,不再接受新任务if (queue.isEmpty())break; long currentTime, executionTime;// 获取queue队列里面下一个要执行的任务(根据时间排序,也就是接下来最近要执行的任务)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;// taskFired表示是否需要立刻执行线程,当task的下次执行时间到达当前时间点时为trueif (taskFired = (executionTime<=currentTime)) {//task.period==0表示这个任务只需要执行一次,这里就从queue里面删掉了if (task.period == 0) { queue.removeMin();task.state = TimerTask.EXECUTED;} else { // Repeating task, reschedule//针对task.period不等于0的任务,则计算它的下次执行时间点//task.period<0表示是fixed delay模式的任务//task.period>0表示是fixed rate模式的任务queue.rescheduleMin(task.period<0 ? currentTime   - task.period: executionTime + task.period);}}}// 如果任务的下次执行时间还没有到达,则挂起TimerThread线程executionTime - currentTime毫秒数,到达执行时间点再自动激活if (!taskFired) queue.wait(executionTime - currentTime);}// 如果任务的下次执行时间到了,则执行任务// 注意:这里任务执行没有另起线程,还是在TimerThread线程执行的,所以当有任务在同时执行时会出现阻塞if (taskFired)  // 这里没有try catch异常,当TimerTask抛出异常会导致整个TimerThread跳出循环,从而导致Timer失效task.run();} catch(InterruptedException e) {}}
}

TimerThread中并没有处理好任务的异常,因此每个TimerTask的实现必须自己try catch防止异常抛出,导致Timer整体失效。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。

schedule与scheduleAtFixedRate区别
在了解schedule与scheduleAtFixedRate方法的区别之前,先看看它们的相同点:

任务执行未超时,下次执行时间 = 上次执行开始时间 + period; 任务执行超时,下次执行时间 = 上次执行结束时间;

在任务执行未超时时,它们都是上次执行时间加上间隔时间,来执行下一次任务。而执行超时时,都是立马执行。

它们的不同点在于侧重点不同,schedule方法侧重保持间隔时间的稳定,而scheduleAtFixedRate方法更加侧重于保持执行频率的稳定。

schedule侧重保持间隔时间的稳定

schedule方法会因为前一个任务的延迟而导致其后面的定时任务延时。计算公式为scheduledExecutionTime(第n+1次) = realExecutionTime(第n次) + periodTime。

也就是说如果第n次执行task时,由于某种原因这次执行时间过长,执行完后的systemCurrentTime>= scheduledExecutionTime(第n+1次),则此时不做时隔等待,立即执行第n+1次task。

而接下来的第n+2次task的scheduledExecutionTime(第n+2次)就随着变成了realExecutionTime(第n+1次)+periodTime。这个方法更注重保持间隔时间的稳定。

scheduleAtFixedRate保持执行频率的稳定

scheduleAtFixedRate在反复执行一个task的计划时,每一次执行这个task的计划执行时间在最初就被定下来了,也就是scheduledExecutionTime(第n次)=firstExecuteTime +n*periodTime。

如果第n次执行task时,由于某种原因这次执行时间过长,执行完后的systemCurrentTime>= scheduledExecutionTime(第n+1次),则此时不做period间隔等待,立即执行第n+1次task。

接下来的第n+2次的task的scheduledExecutionTime(第n+2次)依然还是firstExecuteTime+(n+2)*periodTime这在第一次执行task就定下来了。说白了,这个方法更注重保持执行频率的稳定。

如果用一句话来描述任务执行超时之后schedule和scheduleAtFixedRate的区别就是:schedule的策略是错过了就错过了,后续按照新的节奏来走;scheduleAtFixedRate的策略是如果错过了,就努力追上原来的节奏(制定好的节奏)。

ScheduledExecutorService

基于线程池设计的定时任务解决方案,每个调度任务都会分配到线程池中的一个线程去执行,解决 Timer 定时器无法并发执行的问题,支持 fixedRate 和 fixedDelay。

ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行。也就是说,任务是并发执行,互不影响。

需要注意:只有当执行调度任务时,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态。

ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
<V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);

其中scheduleAtFixedRate和scheduleWithFixedDelay在实现定时程序时比较方便,运用的也比较多。

ScheduledExecutorService中定义的这四个接口方法和Timer中对应的方法几乎一样,只不过Timer的scheduled方法需要在外部传入一个TimerTask的抽象任务。

而ScheduledExecutorService封装的更加细致了,传Runnable或Callable内部都会做一层封装,封装一个类似TimerTask的抽象任务类(ScheduledFutureTask)。然后传入线程池,启动线程去执行该任务。

public class ScheduleAtFixedRateDemo implements Runnable{public static void main(String[] args) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);executor.scheduleAtFixedRate(new ScheduleAtFixedRateDemo(),0,1000,TimeUnit.MILLISECONDS);}@Overridepublic void run() {System.out.println(new Date() + " : 任务「ScheduleAtFixedRateDemo」被执行。");try {Thread.sleep(2000L);} catch (InterruptedException e) {e.printStackTrace();}}
}

上面是scheduleAtFixedRate方法的基本使用方式,但当执行程序时会发现它并不是间隔1秒执行的,而是间隔2秒执行。

这是因为,scheduleAtFixedRate是以period为间隔来执行任务的,如果任务执行时间小于period,则上次任务执行完成后会间隔period后再去执行下一次任务;但如果任务执行时间大于period,则上次任务执行完毕后会不间隔的立即开始下次任务。

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

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

相关文章

理解一致性哈希算法

摘要&#xff1a;一致性哈希是什么&#xff0c;使用场景&#xff0c;解决了什么问题&#xff1f; 本文分享自华为云社区《16 张图解 &#xff5c; 一致性哈希算法》&#xff0c;作者&#xff1a;小林coding。 如何分配请求&#xff1f; 大多数网站背后肯定不是只有一台服务器…

文件格式转换

把我的悲惨故事说给大家乐呵乐呵&#xff1a;老板让运营把一些数据以json格式给我&#xff0c;当我看到运营在石墨文档上编辑的时候我人都傻了&#xff0c;我理解运营的艰难&#xff0c;可我也是真的难啊&#xff0c;在石墨文档编辑的眼花缭乱的&#xff0c;很多属性都错乱了(诸…

[SWPUCTF 2021 新生赛]sql - 联合注入

这题可以参考文章&#xff1a;[SWPUCTF 2021 新生赛]easy_sql - 联合注入||报错注入||sqlmap 这题相比于参考文章的题目多了waf过滤 首先&#xff0c;仍然是网站标题提示参数是wllm 1、fuzz看哪些关键字被过滤&#xff1a;空格、substr、被过滤 2、?wllm-1/**/union/**/selec…

微信小程序 movable-area 区域拖动动态组件演示

movable-area 组件在小程序中的作用是用于创建一个可移动的区域&#xff0c;可以在该区域内拖动视图或内容。这个组件常用于实现可拖动的容器或可滑动的列表等交互效果。 使用 movable-area 组件可以对其内部的 movable-view 组件进行拖动操作&#xff0c;可以通过设置不同的属…

消息驱动 —— SpringCloud Stream

Stream 简介 Spring Cloud Stream 是用于构建消息驱动的微服务应用程序的框架&#xff0c;提供了多种中间件的合理配置 Spring Cloud Stream 包含以下核心概念&#xff1a; Destination Binders&#xff1a;目标绑定器&#xff0c;目标指的是 Kafka 或者 RabbitMQ&#xff0…

信息增益,经验熵和经验条件熵——决策树

目录 1.经验熵 2.经验条件熵 3.信息增益 4.增益比率 5.例子1 6.例子2 在决策树模型中&#xff0c;我们会考虑应该选择哪一个特征作为根节点最好&#xff0c;这里就用到了信息增益 通俗上讲&#xff0c;信息增益就是在做出判断时&#xff0c;该信息对你影响程度的大小。比…

抖音seo源代码开源部署----基于开放平台SaaS服务

抖音SEO搜索是什么&#xff1f; 抖音SEO搜索是指在抖音平台上进行搜索引擎优化&#xff08;Search Engine Optimization&#xff09;的一种技术手段。 通过优化抖音账号、发布内容和关键词等&#xff0c;提高抖音视频在搜索结果中的排名&#xff0c;从而增加视频曝光量和用户点…

ValueError: high is out of bounds for int32 报错

问题描述&#xff1a; 笔者在Windows 64位平台跑一个在Ubuntu上运行正常的程序时&#xff0c;出现了以下报错&#xff1a; 具体为&#xff1a; seed np.random.randint(0, 2 ** 32) # make a seed with numpy generatorFile "mtrand.pyx", line 763, in numpy.ra…

Moonbeam Ignite强势回归

参与Moonbeam上最新的流动性计划 还记得新一轮的流动性激励计划吗&#xff1f;Moonbeam Ignite社区活动带着超过300万枚GLMR奖励来啦&#xff01;体验新项目&#xff0c;顺便薅一把GLMR羊毛。 本次Moonbeam Ignite活动的参与项目均为第二批Moonbeam生态系统Grant资助提案中获…

BaseQuickAdapter触底刷新实现

触底刷新实现 使用BaseQuickAdapter&#xff0c;在适配器中实现 LoadMoreModule即可&#xff0c;如下加上即可&#xff0c;无需多写代码 以下为分页实现&#xff1a; 视图中 // 获取加载更多模块loadMoreModule blogAdapter.getLoadMoreModule();loadMoreModule.setOnLoadMo…

无线振弦采集仪在岩土工程中如何远程监测和远程维护

无线振弦采集仪在岩土工程中如何远程监测和远程维护 随着岩土工程施工的不断发展和科技水平的不断提高&#xff0c;远程监测和远程维护设备也得到了广泛关注和应用。无线振弦采集仪是一种广泛应用于岩土工程中的测量仪器&#xff0c;在现代化施工中扮演着重要的角色。本文将就…

2023学生近视了用什么台灯好呢?好用预防近视的护眼台灯推荐

自从护眼台灯能够帮助孩子在写作业时能够缓解视觉疲劳以来&#xff0c;许多家长已经给孩子安排上来护眼台灯&#xff0c;护眼台灯能够提供良好的照明环境&#xff0c;并且能够让我们专心学习提高工作效率。但由于护眼台灯含有独家的黑科技技术&#xff0c;价格始终居高不下&…

【微信小程序开发】一文学会使用CSS样式布局与美化

引言 在微信小程序开发中&#xff0c;CSS样式布局和美化是非常重要的一部分&#xff0c;它能够为小程序增添美感&#xff0c;提升用户体验。本文将介绍如何学习使用CSS进行样式布局和美化&#xff0c;同时给出代码示例&#xff0c;帮助开发者更好地掌握这一技巧。 一、CSS样式布…

ssm+vue的公司人力资源管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的公司人力资源管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结…

【Overload游戏引擎分析】画场景栅格的Shader分析

Overload引擎地址&#xff1a; GitHub - adriengivry/Overload: 3D Game engine with editor 一、栅格绘制基本原理 Overload Editor启动之后&#xff0c;场景视图中有栅格线&#xff0c;这个在很多软件中都有。刚开始我猜测它应该是通过绘制线实现的。阅读代码发现&#xff0…

漏刻有时物联网环境态势感知大数据(设备列表、动态折线图)

物联网环境下的态势感知是指对物联网环境中的各种要素进行全面、实时、准确的监测、分析和预测,以实现网络态势的全面掌握和安全威胁的及时响应和处理。具体而言,态势感知以物联网环境为基础,利用各类传感器、数据采集设备和其他相关工具,对物联网设备、资产、数据流等进行…

【Python】语言学习

之前总觉得python简单&#xff0c;不当回事&#xff0c;直到自己动手连输出都写不出来。。于是开一篇专门练python的博客。 输出 Python初相识 (educoder.net) 常规输出 print("向上&#xff1a;%.2f,向下&#xff1a;%.2f" %(pow(1.001, 365),pow(0.999, 365))) …

论文笔记 A theory of learning from different domains

domain adaptation 领域理论方向的重要论文. 这篇笔记主要是推导文章中的定理, 还有分析定理的直观解释. 笔记中的章节号与论文中的保持一致. 1. Introduction domain adaptation 的设定介绍: 有两个域, source domain 与 target domain. source domain: 一组从 source dist.…

Java 面向对象的三大特性

面向对象编程有三大特征: 封装、继承和多态。 1.封装 1&#xff09;封装介绍 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起数据被保护在内部.程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。 2&#xff09;封装的理解和好处 隐…

Springboot使用ProcessBuilder创建系统进程执行shell命令备份数据库

文章目录 概要1、查看mysql版本2、相关依赖3、具体代码技术细节 概要 Springboot执行shell命令备份数据库。 1、查看mysql版本 mysql --version2、相关依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-star…