简介
在实现定时调度功能的时候,我们往往会借助于第三方类库来完成,比如: quartz 、 Spring Schedule 等等。JDK从1.3版本开始,就提供了基于 Timer 的定时调度功能。在 Timer 中,任务的执行是串行的。这种特性在保证了线程安全的情况下,往往带来了一些严重的副作用,比如任务间相互影响、任务执行效率低下等问题。为了解决 Timer 的这些问题,JDK从1.5版本开始,提供了基于 ScheduledExecutorService 的定时调度功能。
本节我们主要分析 Timer 的功能。对于 ScheduledExecutorService 的功能,我们将新开一篇文章来讲解。
如何使用
Timer 需要和 TimerTask 配合使用,才能完成调度功能。 Timer 表示调度器, TimerTask 表示调度器执行的任务。任务的调度分为两种:一次性调度和循环调度。下面,我们通过一些例子来了解他们是如何使用的。
1. 一次性调度
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override public void run() {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
System.out.println(format.format(scheduledExecutionTime()) + ", called");
}
};
// 延迟一秒,打印一次
// 打印结果如下:10:58:24, called
timer.schedule(task, 1000);
}
2. 循环调度 - schedule()
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override public void run() {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
System.out.println(format.format(scheduledExecutionTime()) + ", called");
}
};
// 固定时间的调度方式,延迟一秒,之后每隔一秒打印一次
// 打印结果如下:
// 11:03:55, called
// 11:03:56, called
// 11:03:57, called
// 11:03:58, called
// 11:03:59, called
// ...
timer.schedule(task, 1000, 1000);
}
3. 循环调度 - scheduleAtFixedRate()
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override public void run() {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
System.out.println(format.format(scheduledExecutionTime()) + ", called");
}
};
// 固定速率的调度方式,延迟一秒,之后每隔一秒打印一次
// 打印结果如下:
// 11:08:43, called
// 11:08:44, called
// 11:08:45, called
// 11:08:46, called
// 11:08:47, called
// ...
timer.scheduleAtFixedRate(task, 1000, 1000);
}
4. schedule()和scheduleAtFixedRate()的区别
从2和3的结果来看,他们达到的效果似乎是一样的。既然效果一样,JDK为啥要实现为两个方法呢?他们应该有不一样的地方!
在正常的情况下,他们的效果是一模一样的。而在异常的情况下 - 任务执行的时间比间隔的时间更长,他们是效果是不一样的。
schedule() 方法,任务的 下一次执行时间 是相对于 上一次实际执行完成的时间点 ,因此执行时间会不断延后
scheduleAtFixedRate() 方法,任务的 下一次执行时间 是相对于 上一次开始执行的时间点 ,因此执行时间不会延后
由于 Timer 内部是通过单线程方式实现的,所以这两种方式都不存在线程安全的问题
我们先来看看 schedule() 的异常效果:
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override public void run() {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(format.format(scheduledExecutionTime()) + ", called");
}
};
timer.schedule(task, 1000, 2000);
// 执行结果如下:
// 11:18:56, called
// 11:18:59, called
// 11:19:02, called
// 11:19:05, called
// 11:19:08, called
// 11:19:11, called
}
接下来我们看看 scheduleAtFixedRate() 的异常效果:
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override public void run() {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(format.format(scheduledExecutionTime()) + ", called");
}
};
timer.scheduleAtFixedRate(task, 1000, 2000);
// 执行结果如下:
// 11:20:45, called
// 11:20:47, called
// 11:20:49, called</