Java Timer使用讲解
作者:UnicornLien 发布时间:2023-11-28 20:30:33
Timer 详解
Timer
和 TimerTask
用于在后台线程中调度任务的 java.util
类。 TimerTask
负责任务的执行, Timer
负责任务的调度。
定时功能
Timer
提供了三种定时模式:
fixed delay
fixed rate
java.util包下提供了对定时任务的支持,涉及2个类:
Timer:定时器类
TimerTask:任务抽象类
使用该定时任务我们需要继承TimerTask抽象类,覆盖run方法编写任务执行代码,并利用Timer定时器对TimerTask进行调度。
编写一个任务:
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println(DateUtil.formatNow() + " " + Thread.currentThread().getName() + " task run ");
}
};
接着使用Timer对TimerTask进行调度,Timer提供了多种方法,可分为一次性任务和可重复执行任务。
一、一次性任务
一次性任务是指Timer执行一次之后,该任务后续不再执行。
一次性任务包括2个方法,如下:
voidschedule(TimerTasktask,longdelay):延迟delay毫秒后执行一次task
voidschedule(TimerTasktask,Datetime):在指定时间time执行一次task,如果time过期,将会立即执行
二、可重复执行任务
可重复执行任务是指,任务允许按照设定的规则重复执行。
可重复执行任务共有4个方法,分为固定延时 schedule和固定速率 scheduleAtFixedRate:
voidschedule(TimerTasktask,longdelay,longperiod):延迟delay毫秒后执行task,之后每隔period毫秒执行一次task
voidschedule(TimerTasktask,DatefirstTime,longperiod):在指定时间time执行一次task,之后每隔period毫秒执行一次task
voidscheduleAtFixedRate(TimerTasktask,longdelay,longperiod):延迟delay毫秒后执行task,之后每隔period毫秒执行一次task
voidscheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod):在指定时间time执行一次task,之后每隔period毫秒执行一次task
示例1:schedule方法,延迟delay毫秒后执行task,之后每隔period毫秒执行一次task
System.out.println("启动于:" + DateUtil.formatNow());
Timer timer = new Timer("timer");
timer.schedule(task, 1000, 2000);
输出:
启动于:2022-10-31 10:05:15
2022-10-31 10:05:16 timer task run
2022-10-31 10:05:18 timer task run
2022-10-31 10:05:20 timer task run
示例2:schedule在指定时间time执行一次task,之后每隔period毫秒执行一次task
System.out.println("启动于:" + DateUtil.formatNow());
Timer timer = new Timer("timer");
timer.schedule(task, DateUtil.parse("2022-10-31 10:07:00", DateUtil.YYYY_MM_DD_HH24_MM_SS), 2000);
输出:
启动于:2022-10-31 10:06:39
2022-10-31 10:07:00 timer task run
2022-10-31 10:07:02 timer task run
2022-10-31 10:07:04 timer task run
固定延时 schedule 和 固定速率 scheduleAtFixedRate 在正常情况下看起来功能基本是一致的,区别在于当任务耗时超出执行时间间隔period,后续任务被延误时,schedule和scheduleAtFixedRate的处理方式不同,后面介绍。
三、固定延时和固定速率区别(重点)
1. 介绍
由于Timer内部仅维护一个线程来执行所有任务,所以当前一个任务耗时过长,可能会导致后一个任务的执行被延误。
出现任务延误的情况下,固定延时 schedule和 固定速率 scheduleAtFixedRate 的区别就在于,schedule会顺延,而scheduleAtFixedRate会把延误任务立马补上。
在网上看到几个非常恰当的例子,贴上来加深理解。
例1:
暑假到了老师给schedule和scheduleAtFixedRate两个同学布置作业。
老师要求学生暑假每天写2页,30天后完成作业。
这两个学生每天按时完成作业,直到第10天,出了意外,两个学生出去旅游花了5天时间,这5天时间里两个人都没有做作业。任务被拖延了。
这时候两个学生采取的策略就不同了:
schedule重新安排了任务时间,旅游回来的第一天做第11天的任务,第二天做第12天的任务,最后完成任务花了35天。
scheduleAtFixedRate是个守时的学生,她总想按时完成老师的任务,于是在旅游回来的第一天把之前5天欠下的任务以及第16天当天的任务全部完成了,之后还是按照老师的原安排完成作业,最后完成任务花了30天。
例2:
固定速率就好比你今天加班到很晚,但是到了第二天还必须准点到公司上班,如果你一不小心加班到了第二天早上 9 点,你就连休息的时间都没有了。
而固定时延的意思是你必须睡够 8 个小时再过来上班,如果你加班到凌晨 6 点,那就可以下午过来上班了。
固定速率强调准点,固定时延强调间隔。
如果任务必须每天准点调度,那就应该使用固定速率调度,并且要确保每个任务执行时间不要太长,避免超过period间隔。
如果任务需要每隔几分钟跑一次,那就使用固定时延调度,它不是很在乎单个任务要跑多长时间。
我们来模拟一下这个情况。
首先,我们对TimerTask进行修改,让它某一次任务产生大量耗时:
TimerTask task = new TimerTask() {
private int i = 1;
@Override
public void run() {
System.out.print(i + " " + DateUtil.formatNow() + " 开始执行, ");
if(i == 3) {
ThreadUtil.sleep(11 * 1000);
}
System.out.println(DateUtil.formatNow() + " 结束");
i++;
}
};
该任务在执行第3次时,将会休眠11秒,这将会导致延误后续的任务。
2. 固定速率
示例:
Timer timer = new Timer("timer");
timer.scheduleAtFixedRate(task, 5000, 2000);
设定任务延迟5秒后执行第1次任务,之后每2秒执行一次。
输出:
启动于:2022-10-31 15:51:24
1 2022-10-31 15:51:29 开始执行, 2022-10-31 15:51:29 结束
2 2022-10-31 15:51:31 开始执行, 2022-10-31 15:51:31 结束
3 2022-10-31 15:51:33 开始执行, 2022-10-31 15:51:44 结束 *
4 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
5 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
6 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
7 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
8 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
9 2022-10-31 15:51:45 开始执行, 2022-10-31 15:51:45 结束
10 2022-10-31 15:51:47 开始执行, 2022-10-31 15:51:47 结束
11 2022-10-31 15:51:49 开始执行, 2022-10-31 15:51:49 结束
如果不存在第3次耗时11秒的情况下,正常任务执行时间应该为:
启动于:2022-10-31 15:51:24
1 2022-10-31 15:51:29 开始执行, 2022-10-31 15:51:29 结束
2 2022-10-31 15:51:31 开始执行, 2022-10-31 15:51:31 结束
3 2022-10-31 15:51:33 开始执行, 2022-10-31 15:51:33 结束 *
4 2022-10-31 15:51:35 开始执行, 2022-10-31 15:51:35 结束 *
5 2022-10-31 15:51:37 开始执行, 2022-10-31 15:51:37 结束 *
6 2022-10-31 15:51:39 开始执行, 2022-10-31 15:51:39 结束 *
7 2022-10-31 15:51:41 开始执行, 2022-10-31 15:51:41 结束 *
8 2022-10-31 15:51:43 开始执行, 2022-10-31 15:51:43 结束 *
9 2022-10-31 15:51:45 开始执行, 2022-10-31 15:51:45 结束
10 2022-10-31 15:51:47 开始执行, 2022-10-31 15:51:47 结束
11 2022-10-31 15:51:49 开始执行, 2022-10-31 15:51:49 结束
但是在第3次执行任务时因为执行耗时11秒,第4次本该在15:51:35开始执行并完成任务,却到了15:51:44才执行完成,这11秒延误了后续5个任务的正常执行,因此在15:51:44时,scheduleAtFixedRate赶作业把延误的5个任务一起执行了。
最后赶上了原本的进度,第9个任务准时在15:51:45执行。
3. 固定延时
示例:
Timer timer = new Timer("timer");
timer.schedule(task, 5000, 2000);
输出:
启动于:2022-10-31 15:56:59
1 2022-10-31 15:57:04 开始执行, 2022-10-31 15:57:04 结束
2 2022-10-31 15:57:06 开始执行, 2022-10-31 15:57:06 结束
3 2022-10-31 15:57:08 开始执行, 2022-10-31 15:57:19 结束 *
4 2022-10-31 15:57:19 开始执行, 2022-10-31 15:57:19 结束
5 2022-10-31 15:57:21 开始执行, 2022-10-31 15:57:21 结束
6 2022-10-31 15:57:24 开始执行, 2022-10-31 15:57:24 结束
7 2022-10-31 15:57:26 开始执行, 2022-10-31 15:57:26 结束
8 2022-10-31 15:57:28 开始执行, 2022-10-31 15:57:28 结束
9 2022-10-31 15:57:30 开始执行, 2022-10-31 15:57:30 结束
10 2022-10-31 15:57:32 开始执行, 2022-10-31 15:57:32 结束
如果不存在第3次耗时11秒的情况下,正常任务执行时间应该为:
启动于:2022-10-31 15:56:59
1 2022-10-31 15:57:04 开始执行, 2022-10-31 15:57:04 结束
2 2022-10-31 15:57:06 开始执行, 2022-10-31 15:57:06 结束
3 2022-10-31 15:57:08 开始执行, 2022-10-31 15:57:08 结束 *
4 2022-10-31 15:57:10 开始执行, 2022-10-31 15:57:10 结束
5 2022-10-31 15:57:12 开始执行, 2022-10-31 15:57:12 结束
6 2022-10-31 15:57:14 开始执行, 2022-10-31 15:57:14 结束
7 2022-10-31 15:57:16 开始执行, 2022-10-31 15:57:16 结束
8 2022-10-31 15:57:18 开始执行, 2022-10-31 15:57:18 结束
9 2022-10-31 15:57:20 开始执行, 2022-10-31 15:57:20 结束
10 2022-10-31 15:57:22 开始执行, 2022-10-31 15:57:22 结束
使用schedule调度,第4次任务本该在15:57:10开始执行,但由于耗时11秒直到15:57:19才开始。
而第3次任务实际是在19秒完成,完成后又在19秒立即执行第4次,中间少了2秒间隔,第4次完成后接着开始2秒一次,变为了从21秒开始执行第5次。
和我原本的推测不一样的是,本以为19秒完成后,第4次会隔2秒在21秒执行,没想到19秒会立即执行。
猜测与delay参数有关,但调整了delay后仍然一样,完成的那一秒还是会马上再执行第4次任务。
通过以上测试对比,我们可以感受到Timer中固定速率和固定延时的区别,但为了避免出错,使用Timer时应让TimerTask耗时尽可能短。
4. 其他要点
1.以上是仅第3次任务加上了耗时11秒,如果是所有任务都耗时11秒呢?
如果每次任务执行都耗时11秒,那么无论是固定速率还是固定延时,都将是11秒执行一个任务。
2.如果改为schedule(TimerTasktask,DatefirstTime,longperiod)和scheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod)来调度任务,firstTime指定为10点,而当前系统时间为11点,会出现什么情况呢?
虽然firstTime已经过期,但是Timer将会立即开始执行任务,之后按照period间隔重复执行任务。
3.如果TimerTask执行过程中抛出了异常会发生什么事情?
Timer内部仅维护一个线程,当任一TimerTask抛出异常,将导致此线程终止运行,该Timer负责的所有任务都无法执行。
四、调度多个TimerTask
在上一节中,介绍的是一个可重复执行的TimeTask,如果执行耗时大于设定的间隔period,将会影响该TimerTask下一次执行的时间点。
而这一节则是为了单独说明,一个Timer同时调度多个TimeTask也会互相影响。
示例:
TimerTask task1 = new TimerTask() {
private int i = 1;
@Override
public void run() {
System.out.print(i + " task1:" + DateUtil.formatNow() + " 开始执行, ");
ThreadUtil.sleep(11 * 1000);
System.out.println(DateUtil.formatNow() + " 结束");
i++;
}
};
TimerTask task2 = new TimerTask() {
private int i = 1;
@Override
public void run() {
System.out.print(i + " task2:" + DateUtil.formatNow() + " 开始执行, ");
ThreadUtil.sleep(11 * 1000);
System.out.println(DateUtil.formatNow() + " 结束");
i++;
}
};
Timer timer = new Timer("timer");
timer.scheduleAtFixedRate(task1, 5000, 2000);
timer.scheduleAtFixedRate(task2, 5000, 2000);
输出:
1 task1:2022-10-31 16:58:27 开始执行, 2022-10-31 16:58:38 结束
1 task2:2022-10-31 16:58:38 开始执行, 2022-10-31 16:58:49 结束
2 task2:2022-10-31 16:58:49 开始执行, 2022-10-31 16:59:00 结束
2 task1:2022-10-31 16:59:00 开始执行, 2022-10-31 16:59:11 结束
3 task1:2022-10-31 16:59:11 开始执行, 2022-10-31 16:59:22 结束
3 task2:2022-10-31 16:59:22 开始执行, 2022-10-31 16:59:33 结束
4 task2:2022-10-31 16:59:33 开始执行, 2022-10-31 16:59:44 结束
4 task1:2022-10-31 16:59:44 开始执行, 2022-10-31 16:59:55 结束
可以发现,task1和task2其实都没有按照既定时间去执行任务了。
根本原因是在于,Timer内部仅维护一个线程执行所有TimerTask,为了避免错误,一个Timer对象最好仅调度一个TimerTask对象,除非可以确保多个TimerTask之间一定不会相互影响。
因此编写TimerTask时应当自行捕获异常。
五、取消任务
Timer在创建时实际上是默认在内部维护了一个非守护线程,即使任务全部执行完成,线程也并不会销毁。
Timer提供cancel()方法,可以手动调用取消定时器所有的任务,并销毁定时器。
如果想要Timer内部创建的是守护线程,可以使用以下构造方法创建定时器,设置isDaemon为true:
Timer(booleanisDaemon)
Timer(Stringname,booleanisDaemon)
如果没有自己定义name参数,默认Timer内部自动命名为“Timer-递增序号”,作为内部线程的线程名称,在构造方法内启动此线程。
如果是要取消单个任务,可以使用TimerTask的cancel()方法。
当TimerTask调用cancel之后,任务是取消了,但Timer自身并不能马上知道TimerTask被取消,而是在准备执行前才知道,因此Timer内部还维护着这个任务的引用。若希望Timer立即清除引用,可调用Timer.purge()立即执行清除。
来源:https://www.cnblogs.com/ladderx/p/16848561.html


猜你喜欢
- TypeScript简介:TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语
- 1. 什么是对象池对象池,顾名思义就是一定数量的已经创建好的对象(Object)的集合。当需要创建对象时,先在池子中获取,如果池子中没有符合
- 目录写在前面引入guava依赖包怎么做变量转换写在前面有时候需要处理对象属性的getter、setter方法,或者将属性与数据表字段进行相互
- 前言众所周知,微信聊天中我们输入一些关键词会有表情雨下落,比如输入「生日快乐」「么么哒」会有相应的蛋糕、亲吻的表情雨下落,今天就来完成这个表
- 首先设定TextView的clickable属性为true。可以在布局文件中进行设定,比如:<TextViewandroid:id=&
- 1 需求描述我们现在要干一个什么事情呢,我们要在浏览器输入一个请求地址,然后我们的后端就给我返回一个User对象即可,并且我希望以Json的
- 一、二叉排序树定义1.二叉排序树的定义二叉排序树(Binary Sort Tree)又称二叉查找(搜索)树(Binary Search Tr
- 前言空间分配要点有:一是空间分配的连续性;二是动态内存申请;三是防止程序执行中出现异常错误。提示:开始讲解了嗷~后续会根据精力持续更新嗷!!
- 前言Quarkus和RESTEasy团队非常高兴地宣布了Quarkus中的RESTEasy Reactive集成已进入master分支,并将
- 本文章牵涉到的技术点比较多:spring Data JPA、Redis、Spring MVC,Spirng Cache,所以在看这篇文章的时
- final 类final 类不能被继承,同时,一旦用 final 修饰了类,也就意味着 final 类中的所有方法都被隐式地指定为 fina
- 定义享元模式(FlyWeight Pattern),也叫蝇量模式,运用共享技术,有效的支持大量细粒度的对象,享元模式就是池技术的重要实现方式
- 栈(Stack)和队列是非常类似的一个容器,只是栈是一个后进先出(LIFO)的容器。栈用Push()方法在栈中添加元素,用Pop()方法获取
- 1、定义常量(1)const修饰变量,以下两种定义形式在本质上是一样的。它的含义是:const修饰的类型为TYPE的变量value是不可变的
- 元注解是指注解的注解。包括 @Retention @Target @Document @Inherited四种。1. Annotation型
- 有时候编译器、处理器的优化会导致runtime与我们设想的不一样,为此Java对编译器和处理器做了一些限制,JAVA内存模型(JMM)将这些
- 01.点明观点C#中,非托管资源使用之后必须释放,而using()是使用非托管资源的最佳方式,可以确保资源在代码块结束之后被正确释放,并且代
- 不知道有人做没做过对日外包,如果做过的话,那么对vb.net应该非常熟悉了,当年我刚毕业的时候也做过四个月的外包,那种日子简直不是人过的,就
- 在Mybatis的动态SQL和${}形式的参数中都用到了OGNL表达式。Mybatis常用的OGNL表达式如下1、e1 or e2:或<
- 本文实例讲述了C#基于DBContext(EF)实现通用增删改查的REST方法,分享给大家供大家参考。具体如下:我们用ADO.NET Ent