Java Timer使用介绍
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个方法,如下:
- void schedule(TimerTask task, long delay):延迟delay毫秒后执行一次task
- void schedule(TimerTask task, Date time):在指定时间time执行一次task,如果time过期,将会立即执行
二、可重复执行任务
可重复执行任务是指,任务允许按照设定的规则重复执行。
可重复执行任务共有4个方法,分为 固定延时 schedule 和 固定速率 scheduleAtFixedRate:
- void schedule(TimerTask task, long delay, long period):延迟delay毫秒后执行task,之后每隔period毫秒执行一次task
- void schedule(TimerTask task, Date firstTime, long period):在指定时间time执行一次task,之后每隔period毫秒执行一次task
- void scheduleAtFixedRate(TimerTask task, long delay, long period):延迟delay毫秒后执行task,之后每隔period毫秒执行一次task
- void scheduleAtFixedRate(TimerTask task, Date firstTime, long period):在指定时间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. 其他要点
- 以上是仅第3次任务加上了耗时11秒,如果是所有任务都耗时11秒呢?
如果每次任务执行都耗时11秒,那么无论是固定速率还是固定延时,都将是11秒执行一个任务。
- 如果改为schedule(TimerTask task, Date firstTime, long period)和scheduleAtFixedRate(TimerTask task, Date firstTime, long period)来调度任务,firstTime指定为10点,而当前系统时间为11点,会出现什么情况呢?
虽然firstTime已经过期,但是Timer将会立即开始执行任务,之后按照period间隔重复执行任务。
- 如果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(boolean isDaemon)
- Timer(String name, boolean isDaemon)
如果没有自己定义name参数,默认Timer内部自动命名为“Timer-递增序号”,作为内部线程的线程名称,在构造方法内启动此线程。
如果是要取消单个任务,可以使用TimerTask的cancel()方法。
当TimerTask调用cancel之后,任务是取消了,但Timer自身并不能马上知道TimerTask被取消,而是在准备执行前才知道,因此Timer内部还维护着这个任务的引用。若希望Timer立即清除引用,可调用Timer.purge()立即执行清除。
Java Timer使用介绍的更多相关文章
- Java:利用java Timer类实现定时执行任务的功能
一.概述 在java中实现定时执行任务的功能,主要用到两个类,Timer和TimerTask类.其中Timer是用来在一个后台线程按指定的计划来执行指定的任务.TimerTask一个抽象类,它的子类代 ...
- java.util.concurrent介绍【转】
java.util.concurrent介绍 java.util.concurrent 包含许多线程安全.测试良好.高性能的并发构建块.不客气地说,创建 java.util.concurrent ...
- Java定时任务:利用java Timer类实现定时执行任务的功能
一.概述 在java中实现定时执行任务的功能,主要用到两个类,Timer和TimerTask类.其中Timer是用来在一个后台线程按指定的计划来执行指定的任务. TimerTask一个抽象类,它的子类 ...
- JAVA基本类库介绍
我们曾经讲过,Java已经为编程者编制了许多类,这些类已经经过测试,基本上不存在错误,这些类都是我们编程的基础.如果不利用这些已存在的类,我们的 编程工作将变得异常复杂,所以我们应尽可能多的掌握Jav ...
- Java垃圾回收介绍(译)
在Java中,对象内存空间的分配与回收是由JVM中的垃圾回收进程自动完成的.与C语言不同的是,在Java中开发者不需要专门为垃圾回收写代码.这是使Java流行的众多特征之一,也帮助了程序员写出了更好的 ...
- Java Web开发介绍
转自:http://www.cnblogs.com/pythontesting/p/4963021.html Java Web开发介绍 简介 Java很好地支持web开发,在桌面上Eclipse RC ...
- Java Timer触发定时器
XML: <!-- Java Timer定时 --> <!-- <bean id="shortUrlTask" class=" com.sprin ...
- [译]Java 垃圾回收介绍
说明:这篇文章来翻译来自于Javapapers 的Java Garbage Collection Introduction 在Java中,对象内存空间的分配与回收是由JVM中的垃圾回收进程自动完成的. ...
- JAVA Timer定时器使用方法(二)
JAVA Timer 定时器测试 MyTask.java:package com.timer; import java.text.SimpleDateFormat;import java.util. ...
随机推荐
- ES6 Promise详解
前言 本文主要是对Promise本身的用法做一个全面解析而非它的原理实现,如果你对Promise的用法还不是很熟悉或者想加深你对Promise的理解,我相信这篇文章一定会帮到你. 首先让我们先了解一下 ...
- Python自学教程5-字符串有哪些常用操作
任何编程语言,不管是Python.Java 还是 Golang, 字符串都是最重要的一种数据类型. 但是字符串的操作又很多,初学者经常毫无头绪,不知道从哪儿学起,也不知道哪些操作用得多,今天九柄就和你 ...
- 「CCO 2017」专业网络
Kevin 正在一个社区中开发他的专业网络.不幸的是,他是个外地人,还不认识社区中的任何人.但是他可以与 N 个人建立朋友关系 . 然而,社区里没几个人想与一个外地人交朋友.Kevin 想交朋友的 N ...
- 3-12 Python函数定义与调用
Python 函数 函数概念 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.如print() range()函数,但你也可以自己创建函 ...
- java代码审计的点
java代码审计的点 组件的审计 首先看pom.xml查看第三方组件和第三方组件的版本 常用的第三方组件: 第三方组件 漏洞类型 组件漏洞版本 log4j2 远程代码执行 Apache log4j2 ...
- Linux之LVM逻辑卷管理
LVM逻辑卷管理 LVM机制:PV物理卷,VG卷组,LV逻辑卷. --功能-- --物理卷管理-- --卷组管理-- --逻辑卷管理-- create(建立) pvcreate vgcreate lv ...
- 搭建docker镜像仓库(二):使用harbor搭建本地镜像仓库
目录 一.系统环境 二.前言 三.Harbor 四.使用harbor搭建私有镜像仓库 4.1 环境介绍 4.2 k8smaster节点安装配置harbor 4.2.1 安装harbor离线包 4.2. ...
- Linux_ps总结
ps命令用于监测进程的工作情况.进程是一直处于动态变化中,而ps命令所显示的进程工作状态时瞬间的 使用方式: ps [options] 常用参数 -A 显示所有进程 -a 显示现行终端机下的所有进程, ...
- [开源]React/Vue通用的状态管理框架,不好用你来打我👀
为了防止被打,有请"燕双鹰"镇楼️♀️️️...o... 话说新冠3年,"状态管理框架"豪杰并起.群雄逐鹿,ReduxToolkit.Mobx.Vuex. ...
- Promtail Pipeline 日志处理配置
转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247492144&idx=1&sn=a1cc13a642 ...