所有类型的 Java 应用程序一般都需要计划重复执行的任务企业应用程序需要计划每日的日志或者晚间批处理过程一个 JSE 或者 JME 日历应用程序需要根据用户的约定计划闹铃时间不过标准的调度类 Timer 和 TimerTask 没有足够的灵活性无法支持通常需要的计划任务类型在本文中Java 开发人员 Tom White 向您展示了如何构建一个简单通用的计划框架以用于执行任意复杂的计划任务
我将把 javautilTimer 和 javautilTimerTask 统称为 Java 计时器框架它们使程序员可以很容易地计划简单的任务(注意这些类也可用于 JME 中)在 Java SDK Standard Edition Version 中引入这个框架之前开发人员必须编写自己的调度程序这需要花费很大精力来处理线程和复杂的 Objectwait() 方法不过Java 计时器框架没有足够的能力来满足许多应用程序的计划要求甚至一项需要在每天同一时间重复执行的任务也不能直接使用 Timer 来计划因为在夏令时开始和结束时会出现时间跳跃
本文展示了一个通用的 Timer 和 TimerTask 计划框架从而允许更灵活的计划任务这个框架非常简单 —— 它包括两个类和一个接口 —— 并且容易掌握如果您习惯于使用 Java 定时器框架那么您应该可以很快地掌握这个计划框架
计划单次任务
计划框架建立在 Java 定时器框架类的基础之上因此在解释如何使用计划框架以及如何实现它之前我们将首先看看如何用这些类进行计划
想像一个煮蛋计时器在数分钟之后(这时蛋煮好了)它会发出声音提醒您清单 中的代码构成了一个简单的煮蛋计时器的基本结构它用 Java 语言编写
清单 EggTimer 类
package orgtilingschedulingexamples;
import javautilTimer;
import javautilTimerTask;
public class EggTimer {
private final Timer timer = new Timer();
private final int minutes;
public EggTimer(int minutes) {
thisminutes = minutes;
}
public void start() {
timerschedule(new TimerTask() {
public void run() {
playSound();
timercancel();
}
private void playSound() {
Systemoutprintln(Your egg is ready!);
// Start a new thread to play a sound
}
} minutes * * );
}
public static void main(String[] args) {
EggTimer eggTimer = new EggTimer();
eggTimerstart();
}
}
EggTimer 实例拥有一个 Timer 实例用于提供必要的计划用 start() 方法启动煮蛋计时器后它就计划了一个 TimerTask在指定的分钟数之后执行时间到了Timer 就在后台调用 TimerTask 的 start() 方法这会使它发出声音在取消计时器后这个应用程序就会中止
计划重复执行的任务
通过指定一个固定的执行频率或者固定的执行时间间隔Timer 可以对重复执行的任务进行计划不过有许多应用程序要求更复杂的计划例如每天清晨在同一时间发出叫醒铃声的闹钟不能简单地使用固定的计划频率 毫秒( 小时)因为在钟拨快或者拨慢(如果您的时区使用夏令时)的那些天里叫醒可能过晚或者过早解决方案是使用日历算法计算每日事件下一次计划发生的时间而这正是计划框架所支持的考虑清单 中的 AlarmClock 实现
清单 AlarmClock 类
package orgtilingschedulingexamples;
import javatextSimpleDateFormat;
import javautilDate;
import orgtilingschedulingScheduler;
import orgtilingschedulingSchedulerTask;
import orgtilingscheratorsDailyIterator;
public class AlarmClock {
private final Scheduler scheduler = new Scheduler();
private final SimpleDateFormat dateFormat =
new SimpleDateFormat(dd MMM yyyy HH:mm:ssSSS);
private final int hourOfDay minute second;
public AlarmClock(int hourOfDay int minute int second) {
thishourOfDay = hourOfDay;
thisminute = minute;
thissecond = second;
}
public void start() {
schedulerschedule(new SchedulerTask() {
public void run() {
soundAlarm();
}
private void soundAlarm() {
Systemoutprintln(Wake up! +
Its + dateFormatformat(new Date()));
// Start a new thread to sound an alarm
}
} new DailyIterator(hourOfDay minute second));
}
public static void main(String[] args) {
AlarmClock alarmClock = new AlarmClock( );
alarmClockstart();
}
}
注意这段代码与煮蛋计时器应用程序非常相似AlarmClock 实例拥有一个 Scheduler (而不是 Timer)实例用于提供必要的计划启动后这个闹钟对 SchedulerTask (而不是 TimerTask)进行调度用以发出报警声这个闹钟不是计划一个任务在固定的延迟时间后执行而是用 DailyIterator 类描述其计划在这里它只是计划任务在每天上午 : 执行下面是一个正常运行情况下的输出
Wake up! Its Aug ::
Wake up! Its Aug ::
Wake up! Its Aug ::
Wake up! Its Aug ::
Wake up! Its Aug ::
DailyIterator 实现了 ScheduleIterator这是一个将 SchedulerTask 的计划执行时间指定为一系列 javautilDate 对象的接口然后 next() 方法按时间先后顺序迭代 Date 对象返回值 null 会使任务取消(即它再也不会运行)—— 这样的话试图再次计划将会抛出一个异常清单 包含 ScheduleIterator 接口
清单 ScheduleIterator 接口
package orgtilingscheduling;
import javautilDate;
public interface ScheduleIterator {
public Date next();
}
DailyIterator 的 next() 方法返回表示每天同一时间(上午 :)的 Date 对象如清单 所示所以如果对新构建的 next() 类调用 next()那么将会得到传递给构造函数的那个日期当天或者后面一天的 : AM再次调用 next() 会返回后一天的 : AM如此重复为了实现这种行为DailyIterator 使用了 javautilCalendar 实例构造函数会在日历中加上一天对日历的这种设置使得第一次调用 next() 会返回正确的 Date注意代码没有明确地提到夏令时修正因为 Calendar 实现(在本例中是 GregorianCalendar)负责对此进行处理所以不需要这样做
清单 DailyIterator 类
package orgtilingscherators;
import orgtilingschedulingScheduleIterator;
import javautilCalendar;
import javautilDate;
/**
* A DailyIterator class returns a sequence of dates on subsequent days
* representing the same time each day
*/
public class DailyIterator implements ScheduleIterator {
private final int hourOfDay minute second;
private final Calendar calendar = CalendargetInstance();
public DailyIterator(int hourOfDay int minute int second) {
this(hourOfDay minute second new Date());
}
public DailyIterator(int hourOfDay int minute int second Date date) {
thishourOfDay = hourOfDay;
thisminute = minute;
thissecond = second;
calendarsetTime(date);
calendarset(CalendarHOUR_OF_DAY hourOfDay);
calendarset(CalendarMINUTE minute);
calendarset(CalendarSECOND second);
calendarset(CalendarMILLISECOND );
if (!calendargetTime()before(date)) {
calendaradd(CalendarDATE );
}
}
public Date next() {
calendaradd(CalendarDATE );
return calendargetTime();
}
}