实现计划框架
在上一节,我们学习了如何使用计划框架,并将它与 Java 定时器框架进行了比较。下面,我将向您展示如何实现这个框架。除了 清单 3 中展示的 ScheduleIterator 接口,构成这个框架的还有另外两个类 ―― Scheduler 和 SchedulerTask 。这些类实际上在内部使用 Timer 和 SchedulerTask,因为计划其实就是一系列的单次定时器。
清单 5 和 6 显示了这两个类的源代码:
清单 5. Scheduler
package org.tiling.scheduling;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Scheduler {
class SchedulerTimerTask extends TimerTask {
private SchedulerTask schedulerTask;
private ScheduleIterator iterator;
public SchedulerTimerTask(SchedulerTask schedulerTask,
ScheduleIterator iterator) {
this.schedulerTask = schedulerTask;
this.iterator = iterator;
}
public void run() {
schedulerTask.run();
reschedule(schedulerTask, iterator);
}
}
private final Timer timer = new Timer();
public Scheduler() {
}
public void cancel() {
timer.cancel();
}
public void schedule(SchedulerTask schedulerTask,
ScheduleIterator iterator) {
Date time = iterator.next();
if (time == null) {
schedulerTask.cancel();
} else {
synchronized(schedulerTask.lock) {
if (schedulerTask.state != SchedulerTask.VIRGIN) {
throw new IllegalStateException("Task already
scheduled " + "or cancelled");
}
schedulerTask.state = SchedulerTask.SCHEDULED;
schedulerTask.timerTask =
new SchedulerTimerTask(schedulerTask, iterator);
timer.schedule(schedulerTask.timerTask, time);
}
}
}
private void reschedule(SchedulerTask schedulerTask,
ScheduleIterator iterator) {
Date time = iterator.next();
if (time == null) {
schedulerTask.cancel();
} else {
synchronized(schedulerTask.lock) {
if (schedulerTask.state != SchedulerTask.CANCELLED) {
schedulerTask.timerTask =
new SchedulerTimerTask(schedulerTask, iterator);
timer.schedule(schedulerTask.timerTask, time);
}
}
}
}
}
清单 6 显示了 SchedulerTask 类的源代码:
package org.tiling.scheduling;
import java.util.TimerTask;
public abstract class SchedulerTask implements Runnable {
final Object lock = new Object();
int state = VIRGIN;
static final int VIRGIN = 0;
static final int SCHEDULED = 1;
static final int CANCELLED = 2;
TimerTask timerTask;
protected SchedulerTask() {
}
public abstract void run();
public boolean cancel() {
synchronized(lock) {
if (timerTask != null) {
timerTask.cancel();
}
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}
public long scheduledExecutionTime() {
synchronized(lock) {
return timerTask == null ? 0 : timerTask.scheduledExecutionTime();
}
}
}
就像煮蛋计时器,Scheduler 的每一个实例都拥有 Timer 的一个实例,用于提供底层计划。Scheduler 并没有像实现煮蛋计时器时那样使用一个单次定时器,它将一组单次定时器串接在一起,以便在由 ScheduleIterator 指定的各个时间执行 SchedulerTask 类。
考虑 Scheduler 上的 public schedule() 方法 ―― 这是计划的入口点,因为它是客户调用的方法(在 取消任务 一节中将描述仅有的另一个 public 方法 cancel())。通过调用 ScheduleIterator 接口的 next(),发现第一次执行 SchedulerTask 的时间。然后通过调用底层 Timer 类的单次 schedule() 方法,启动计划在这一时刻执行。为单次执行提供的 TimerTask 对象是嵌入的 SchedulerTimerTask 类的一个实例,它包装了任务和迭代器(iterator)。在指定的时间,调用嵌入类的 run() 方法,它使用包装的任务和迭代器引用以便重新计划任务的下一次执行。reschedule() 方法与 schedule() 方法非常相似,只不过它是 private 的,并且执行一组稍有不同的 SchedulerTask 状态检查。重新计划过程反复重复,为每次计划执行构造一个新的嵌入类实例,直到任务或者调度程序被取消(或者 JVM 关闭)。
类似于 TimerTask,SchedulerTask 在其生命周期中要经历一系列的状态。创建后,它处于 VIRGIN 状态,这表明它从没有计划过。计划以后,它就变为 SCHEDULED 状态,再用下面描述的方法之一取消任务后,它就变为 CANCELLED 状态。管理正确的状态转变 ―― 如保证不对一个非 VIRGIN 状态的任务进行两次计划 ―― 增加了 Scheduler 和 SchedulerTask 类的复杂性。在进行可能改变任务状态的操作时,代码必须同步任务的锁对象。
取消任务
取消计划任务有三种方式。第一种是调用 SchedulerTask 的 cancel() 方法。这很像调用 TimerTask 的 cancel()方法:任务再也不会运行了,不过已经运行的任务仍会运行完成。 cancel() 方法的返回值是一个布尔值,表示如果没有调用 cancel() 的话,计划的任务是否还会运行。更准确地说,如果任务在调用 cancel() 之前是 SCHEDULED 状态,那么它就返回 true。如果试图再次计划一个取消的(甚至是已计划的)任务,那么 Scheduler 就会抛出一个 IllegalStateException。
取消计划任务的第二种方式是让 ScheduleIterator 返回 null。这只是第一种方式的简化操作,因为 Scheduler 类调用 SchedulerTask 类的 cancel()方法。如果您想用迭代器而不是任务来控制计划停止时间时,就用得上这种取消任务的方式了。
第三种方式是通过调用其 cancel() 方法取消整个 Scheduler。这会取消调试程序的所有任务,并使它不能再计划任何任务。
扩展 cron 实用程序
可以将计划框架比作 UNIX 的 cron 实用程序,只不过计划次数的规定是强制性而不是声明性的。例如,在 AlarmClock 实现中使用的 DailyIterator 类,它的计划与 cron 作业的计划相同,都是由以 0 7 * * * 开始的 crontab 项指定的(这些字段分别指定分钟、小时、日、月和星期)。
不过,计划框架比 cron 更灵活。想像一个在早晨打开热水的 HeatingController 应用程序。我想指示它“在每个工作日上午 8:00 打开热水,在周未上午 9:00 打开热水”。使用 cron,我需要两个 crontab 项(0 8 * * 1,2,3,4,5 和 0 9 * * 6,7)。而使用 ScheduleIterator 的解决方案更简洁一些,因为我可以使用复合(composition)来定义单一迭代器。清单 7 显示了其中的一种方法:
清单 7. 用复合定义单一迭代器
int[] weekdays = new int[] {
Calendar.MONDAY,
Calendar.TUESDAY,
Calendar.WEDNESDAY,
Calendar.THURSDAY,
Calendar.FRIDAY
};
int[] weekend = new int[] {
Calendar.SATURDAY,
Calendar.SUNDAY
};
ScheduleIterator i = new CompositeIterator(
new ScheduleIterator[] {
new RestrictedDailyIterator(8, 0, 0, weekdays),
new RestrictedDailyIterator(9, 0, 0, weekend)
}
);
RestrictedDailyIterator 类很像 DailyIterator,只不过它限制为只在一周的特定日子里运行,而一个 CompositeIterator 类取得一组 ScheduleIterators,并将日期正确排列到单个计划中。
有许多计划是 cron 无法生成的,但是 ScheduleIterator 实现却可以。例如,“每个月的最后一天”描述的计划可以用标准 Java 日历算法来实现(用 Calendar 类),