定时器(Timer)是一个工具类,用于安排任务(java.util.TimerTask)在指定时间后执行或以指定的时间间隔重复执行。它可以用于执行定时任务、定时调度和时间延迟等操作。
定时器(Timer)可以应用于许多场景,比如:
调度任务(固定速率):当你需要按照预定时间执行任务时,可以使用定时器。例如,每天凌晨执行数据备份、定时生成报表、定时发送通知等。即scheduleAtFixedRate的2个重载方法。
超时处理(固定延迟):当你需要处理某个操作的超时情况时,可以使用定时器。例如,设置一个操作的超时时间,如果在规定时间内未完成,则执行相应的超时处理逻辑。即schedule的4个重载方法。
Java中的定时器:java.util.Timer,它的常用方法:
Java 8 中文版 - 在线API手册 - 码工具
Modifier and Type |
Method and Description |
参数说明 |
void |
cancel() 终止此计时器,丢弃任何当前计划的任务。 |
/ |
int |
purge() 从该计时器的任务队列中删除所有取消的任务。 |
/ |
void |
schedule(TimerTask task, Date time) 在指定的时间安排指定的任务执行。如果此时间已过去,则安排立即执行该任务 |
task:要调度的任务 time:执行任务的时间 |
void |
schedule(TimerTask task, Date firstTime, long period) 从指定 的时间开始 ,对指定的任务执行重复的 固定延迟执行 。 |
task:要调度的任务 firstTime:第一次执行任务的时间 period:连续任务以毫秒为单位的时间间隔 |
void |
schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。 |
task:要调度的任务 delay:在执行任务之前,以毫秒为单位进行延迟的时间 |
void |
schedule(TimerTask task, long delay, long period) 在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。 |
task:要调度的任务 delay:在执行任务之前,以毫秒为单位进行延迟的时间 period:连续任务以毫秒为单位的时间间隔 |
void |
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 从指定的时间 开始 ,对指定的任务执行重复的 固定速率执行 。 |
task:要调度的任务 firstTime:第一次执行任务的时间 period:连续任务以毫秒为单位的时间间隔 |
void |
scheduleAtFixedRate(TimerTask task, long delay, long period) 在指定的延迟之后 开始 ,重新执行 固定速率的指定任务。 |
task:要调度的任务 delay:在执行任务之前,以毫秒为单位进行延迟的时间 period:连续任务以毫秒为单位的时间间隔 |
这两个方法都是任务调度方法,他们之间区别是,schedule会保证任务的间隔是按照定义的period参数严格执行的,如果某一次调度时间比较长,那么后面的时间会顺延,保证调度间隔都是period,而scheduleAtFixedRate是严格按照调度时间来的,如果某次调度时间太长了,那么会通过缩短间隔的方式保证下一次调度在预定时间执行。举个栗子:你每个3秒调度一次,那么正常就是0,3,6,9s这样的时间,如果第二次调度花了2s的时间,如果是schedule,就会变成0,3+2,8,11这样的时间,保证间隔,而scheduleAtFixedRate就会变成0,3+2,6,9,压缩间隔,保证调度时间。
我们要实现一个定时任务,只需要实现TimerTask的run方法即可。每一个任务都有下一次执行时间nextExecutionTime(毫秒),如果是周期性的任务,那么每次执行都会更新这个时间为下一次的执行时间,当nextExecutionTime小于当前时间时,都会执行它。
(1)第一步:创建一个Timer。
(2)第二步:创建一个TimerTask。
(3)第三步:使用Timer执行TimerTask。
其中第三步无疑是我们目前最关心的,也就是timer.schedule(myTask, 2000L, 1000L)。他的意思是myTask在两秒钟之后开始第一次执行,然后每隔一秒执行一次。这只是最基本的用法。就体现了Timer定时执行的流程。
在2秒后开始执行,只执行一次
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimeTest {
public static void main(String[] args) {
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss:SSS").format(new Date()));
Timer timer = new Timer(); // (1)第一步:创建一个Timer。
timer.schedule(new TimerTask() { // (2)第二步:创建一个TimerTask。(3)第三步:使用Timer执行TimerTask。
@Override
public void run() {
System.out.println("Timer is running");
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss:SSS").format(new Date()));
}
}, 2000);
}
}
运行结果:
当前时间:2023-08-19 22-45-46:161
Timer is running
当前时间:2023-08-19 22-45-48:169
执行周期性任务,只需要添加schedule的第三个参数period。
在2秒后开始执行,每隔1秒执行一次
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimeTest {
public static void main(String[] args) {
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss:SSS").format(new Date()));
Timer timer = new Timer(); // (1)第一步:创建一个Timer。
timer.schedule(new TimerTask() { // (2)第二步:创建一个TimerTask。(3)第三步:使用Timer执行TimerTask。
@Override
public void run() {
System.out.println("Timer is running");
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss:SSS").format(new Date()));
}
}, 2000, 1000);
}
}
运行结果:
当前时间:2023-08-19 22-48-10:190
Timer is running
当前时间:2023-08-19 22-48-12:200
Timer is running
当前时间:2023-08-19 22-48-13:203
Timer is running
当前时间:2023-08-19 22-48-14:216
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimeTest {
public static void main(String[] args) {
System.out.println("timer当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss:SSS").format(new Date()));
Timer timer = new Timer(); // (1)第一步:创建一个Timer。
timer.schedule(new TimerTask() { // (2)第二步:创建一个TimerTask。(3)第三步:使用Timer执行TimerTask。
@Override
public void run() {
System.out.println("Timer is running");
System.out.println("timer当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss:SSS").format(new Date()));
}
}, 2000, 1000);
System.out.println("timer2当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss:SSS").format(new Date()));
Timer timer2 = new Timer(); // (1)第一步:创建一个Timer。
timer2.schedule(new TimerTask() { // (2)第二步:创建一个TimerTask。(3)第三步:使用Timer执行TimerTask。
@Override
public void run() {
System.out.println("Timer2 is running");
System.out.println("timer2当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss:SSS").format(new Date()));
}
}, 3000, 2000);
}
}
运行结果:
timer当前时间:2023-08-20 00-08-06:746
timer2当前时间:2023-08-20 00-08-06:748
Timer is running
timer当前时间:2023-08-20 00-08-08:750
Timer is running
Timer2 is running
timer2当前时间:2023-08-20 00-08-09:755
timer当前时间:2023-08-20 00-08-09:755
Timer is running
timer当前时间:2023-08-20 00-08-10:769
Timer2 is running
timer2当前时间:2023-08-20 00-08-11:768
Timer is running
timer当前时间:2023-08-20 00-08-11:784
Timer is running
timer当前时间:2023-08-20 00-08-12:787
Timer2 is running
timer2当前时间:2023-08-20 00-08-13:770
1、由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。
2、如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。
3、Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。
由于Timer存在上面说的这些缺陷,在JDK1.5中,我们可以使用ScheduledThreadPoolExecutor来代替它,使用Executors.newScheduledThreadPool工厂方法或使用ScheduledThreadPoolExecutor的构造函数来创建定时任务,它是基于线程池的实现,不会存在Timer存在的上述问题,当线程数量为1时,它相当于Timer。