1.概述
Timer进行一个完整的定时任务需要结合TimerTask类一起使用。
Timer:是JDK提供的一个定时器工具,使用时会在主线程之外开启一个单独的线程用于执行指定的计划任务。可安排任务执行一次(延迟执行)或者定期重复执行多次。
TimerTask:是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。
我们可以这样理解,Timer是一个定时调度工具,TimerTask是一个指定的任务。需要定时循环一次或多次完成一个任务时,由Timer调度Timer完成这项任务。Timer是一个调度员,TimerTask代表的是具体任务。
2.Timer类中的6种构造方法:
1).schedule(TimerTask task, Date time):安排任务在指定时间time执行一次任务;
2).schedule(TimerTask task, Date firstTime, long period) :安排任务在指定时间firstTime开始以固定间隔period重复执行任务;
3).schedule(TimerTask task, long delay):安排任务在指定延迟delay秒后执行,只执行一次
4).schedule(TimerTask task, long delay, long period):安排任务从指定的延迟delay秒后以固定间隔period重复执行任务
5).scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排任务在指定时间firstTime开始以固定速率(period秒)重复执行任务
6).scheduleAtFixedRate(TimerTask task, long delay, long period):安排任务在指定的延迟delay秒后开始以固定速率(period秒)重复执行任务
文章末尾有Timer源码。
3.注意事项
3.1 关于时间间隔period的问题。这里仅仅说明 schedule 中的时间间隔 period。schedule 中的period是紧邻的两次任务的实际执行时间的时间差,并不是上次任务执行结束时间与下次任务的实际执行开始时间的时间差,所以理论上讲,如果执行任务的总耗时 totlaTime < period 时,period = 下次任务实际执行时间 - 上次任务实际执行时间。如果执行任务的总耗时 totalTime > period 时,下次任务的执行时间就是上次任务的结束时间,中间没有间隔。如果period为0,则任务只执行一次。
3.2 关于time,firstTime的问题,构造方法 1),2),5) 中都指定了任务的开始时间为time,如果time < systemCurrentTime,该定时任务会立即开始执行,如果time > systemCurrentTime,该定时任务到了指定的time时才执行。
3.3 源码解释
* <p>In fixed-delay execution, each execution is scheduled relative to
* the actual execution time of the previous execution. If an execution
* is delayed for any reason (such as garbage collection or other
* background activity), subsequent executions will be delayed as well.
* In the long run, the frequency of execution will generally be slightly
* lower than the reciprocal of the specified period (assuming the system
* clock underlying <tt>Object.wait(long)</tt> is accurate). As a
* consequence of the above, if the scheduled first time is in the past,
* it is scheduled for immediate execution.
public void schedule(TimerTask task, Date firstTime, long period) {
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, firstTime.getTime(), -period);
}
翻译为中文的大概意思
* 在固定延迟执行中,每次执行都是相对于前一次执行的实际执行时间来安排的。
* 如果由于任何原因(如垃圾收集或其他后台活动)执行被延迟,后续的执行也会被延迟。
* 从长远来看,执行的频率通常会略低于指定周期的倒数(假设< tt>Object.wait(long)</tt>
* 的系统时钟是准确的)。由于上述原因,如果计划的第一次是过去的,则计划立即执行
4.schedule与scheduleAtFixedRate的区别
从构造方法上的区别主要是scheduleAtFixedRate后面多了FixedRate,翻译为中文就是“固定速率”。
2)和5)的区别:
2)是以固定延迟执行任务,5)是以固定速率执行任务,2)重复执行任务的过程中上一个任务的执行耗时会影响下一 个任务的执行时间,可以理解为串联执行任务;上一个任务执行完毕后,如果当前时间-上次任务执行时间>任务间隔时间, 则下一次任务立即执行(上次任务的完成时间就是下次任务的执行时间),如果当前时间-上次任务执行时间<任务间隔时 间,则下次任务在 上次任务执行时间+任务间隔时间后开始执行。测试结果见测试2,4,7,8。
5)重复执行任务的过程中上一个任务的执行耗时不会影响一个任务的执行时间。每次任务的执行时间都是在第一次任务开始的时间戳上加上 (n-1)*时间间隔 。测试结果见栗子5,6,9,10.
4)和6)的区别同上。
具体的区别见6测试结果总结。
5.测试结果分析
创建任务Timer:
Timer timer = new Timer();
创建TimerTask:
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("任务执行时间1:" + this.scheduledExecutionTime());
testRun(200000);
/*try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
// this.cancel(); // 将该任务从任务队列中清除
// timer.cancel(); // 清除任务队列中的全部任务
// TimerTask.cancel() 和 Timer.cancel() 是有区别的,使用过程中需要注意。
// java.util.TimerTask.scheduledExecutionTime() 方法用于返回此任务最近实际执行的安排执行的时间
}
};
java.util.TimerTask.scheduledExecutionTime() 方法用于返回此任务最近实际安排执行的时间.
TimerTask.cancel() 与 Timer.cancel() 区别见另一篇文章 Timer.cancel() 与 TimerTask.cancel()的区别.
创建testRun()
public static void testRun(int times) {
System.err.println("任务开始时间2:" + System.currentTimeMillis());
for (int i = 0; i < times; i++) {
String str = "111";
str += "222";
str += "333";
str += "444";
str += "555";
}
System.err.println("任务完成时间3:" + System.currentTimeMillis());
}
先测试times = 200000 时 testRun()的运行时间,测试结果为
任务开始时间2:1547909480928
任务完成时间3:1547909481006
任务开始时间2:1547909491097
任务完成时间3:1547909491172
任务开始时间2:1547909503956
任务完成时间3:1547909504027
多次测试后,testRun()每次的运行时长大约为 70ms
测试1和测试6主要测试任务时长小于任务间隔时间的情况
测试1:TimerTask.schedule(TimerTask task, Date time)
Date date = sdf.parse("2019-01-19 22:57:30");
timer.schedule(task, date);
测试结果
任务执行时间1:1547909850000
任务开始时间2:1547909850001
任务完成时间3:1547909850092
测试2:TimerTask.schedule(TimerTask task, Date firstTime, long period)
Date date = sdf.parse("2019-01-19 22:59:15");
long period = 1000; // 任务间隔毫秒数
timer.schedule(task, date, period);
测试结果:
任务执行时间1:1547909955000
任务开始时间2:1547909955004
任务完成时间3:1547909955081
任务执行时间1:1547909956001
任务开始时间2:1547909956001
任务完成时间3:1547909956038
任务执行时间1:1547909957002
任务开始时间2:1547909957002
任务完成时间3:1547909957026
任务执行时间1:1547909958002
任务开始时间2:1547909958002
任务完成时间3:1547909958045
任务执行时间1:1547909959003
任务开始时间2:1547909959003
任务完成时间3:1547909959034
测试结果分析:
从测试结果我们可以看出,当任务执行时长小于任务间隔时间时,每次任务之间的间隔时间大约都是1000ms。相差的几个毫秒是由于当前这个线程在执行垃圾回收等等其它任务造成任务的延迟。
测试3:TimerTask.schedule(TimerTask task, long delay)
long delay = 1000; // 任务延迟毫秒数
timer.schedule(task, delay);
测试结果
任务执行时间1:1547910433033
任务开始时间2:1547910433033
任务完成时间3:1547910433111
测试4:TimerTask.schedule(TimerTask task, long delay, long period)
long delay = 1000; // 任务延迟毫秒数
long period = 1000; // 任务间隔毫秒数
timer.schedule(task, delay, period);
测试结果
任务执行时间1:1547910927695
任务开始时间2:1547910927695
任务完成时间3:1547910927745
任务执行时间1:1547910928699
任务开始时间2:1547910928699
任务完成时间3:1547910928733
任务执行时间1:1547910929699
任务开始时间2:1547910929699
任务完成时间3:1547910929715
任务执行时间1:1547910930702
任务开始时间2:1547910930702
任务完成时间3:1547910930735
从测试结果我们可以看出,当任务执行时长小于任务间隔时间时,每次任务之间的间隔时间大约都是1000ms。相差的几个毫秒是由于当前这个线程在执行垃圾回收等等其它任务造成任务的延迟。
测试5:TimerTask.scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
Date date = sdf.parse("2019-01-19 22:59:15");
long period = 1000; // 任务间隔毫秒数
timer.scheduleAtFixedRate(task, date, period);
测试结果
任务执行时间1:1547911150000
任务开始时间2:1547911150008
任务完成时间3:1547911150074
任务执行时间1:1547911151000
任务开始时间2:1547911151009
任务完成时间3:1547911151042
任务执行时间1:1547911152000
任务开始时间2:1547911152011
任务完成时间3:1547911152029
任务执行时间1:1547911153000
任务开始时间2:1547911153007
任务完成时间3:1547911153038
测试结果分析:
从测试结果我们可以看出,当任务执行时长小于任务间隔时间时,每次任务之间的间隔时间都是绝对的1000ms。每次到了任务的预定时间就开始执行下次任务。
测试6&#x