在Java应用程序中计划重复执行的任务介绍Java语言中Timer类的一种简洁用法TomWhite(tom@tiling.org),首席Java开发人员,Kizoom简介:所有类型的Java应用程序一般都需要计划重复执行的任务。企业应用程序需要计划每日的日志或者晚间批处理过程。一个J2SE或者J2ME日历应用程序需要根据用户的约定计划闹铃时间。不过,标准的调度类Timer和TimerTask没有足够的灵活性,无法支持通常需要的计划任务类型。在本文中,Java开发人员TomWhite向您展示了如何构建一个简单通用的计划框架,以用于执行任意复杂的计划任务。发布日期:2003年12月01日级别:中级访问情况2次浏览建议:0(添加评论)平均分(共0个评分)我将把java.util.Timer和java.util.TimerTask统称为Java计时器框架,它们使程序员可以很容易地计划简单的任务(注意这些类也可用于J2ME中)。在Java2SDK,StandardEdition,Version1.3中引入这个框架之前,开发人员必须编写自己的调度程序,这需要花费很大精力来处理线程和复杂的Object.wait()方法。不过,Java计时器框架没有足够的能力来满足许多应用程序的计划要求。甚至一项需要在每天同一时间重复执行的任务,也不能直接使用Timer来计划,因为在夏令时开始和结束时会出现时间跳跃。本文展示了一个通用的Timer和TimerTask计划框架,从而允许更灵活的计划任务。这个框架非常简单――它包括两个类和一个接口――并且容易掌握。如果您习惯于使用Java定时器框架,那么您应该可以很快地掌握这个计划框架(有关Java定时器框架的更多信息,请参阅参考资料)。计划单次任务计划框架建立在Java定时器框架类的基础之上。因此,在解释如何使用计划框架以及如何实现它之前,我们将首先看看如何用这些类进行计划。想像一个煮蛋计时器,在数分钟之后(这时蛋煮好了)它会发出声音提醒您。清单1中的代码构成了一个简单的煮蛋计时器的基本结构,它用Java语言编写:清单1.EggTimer类packageorg.tiling.scheduling.examples;importjava.util.Timer;importjava.util.TimerTask;publicclassEggTimer{privatefinalTimertimer=newTimer();privatefinalintminutes;publicEggTimer(intminutes){this.minutes=minutes;}publicvoidstart(){timer.schedule(newTimerTask(){publicvoidrun(){playSound();timer.cancel();}privatevoidplaySound(){System.out.println(Youreggisready!);//Startanewthreadtoplayasound...}},minutes*60*1000);}publicstaticvoidmain(String[]args){EggTimereggTimer=newEggTimer(2);eggTimer.start();}}EggTimer实例拥有一个Timer实例,用于提供必要的计划。用start()方法启动煮蛋计时器后,它就计划了一个TimerTask,在指定的分钟数之后执行。时间到了,Timer就在后台调用TimerTask的start()方法,这会使它发出声音。在取消计时器后这个应用程序就会中止。计划重复执行的任务通过指定一个固定的执行频率或者固定的执行时间间隔,Timer可以对重复执行的任务进行计划。不过,有许多应用程序要求更复杂的计划。例如,每天清晨在同一时间发出叫醒铃声的闹钟不能简单地使用固定的计划频率86400000毫秒(24小时),因为在钟拨快或者拨慢(如果您的时区使用夏令时)的那些天里,叫醒可能过晚或者过早。解决方案是使用日历算法计算每日事件下一次计划发生的时间。而这正是计划框架所支持的。考虑清单2中的AlarmClock实现(有关计划框架的源代码以及包含这个框架和例子的JAR文件,请参阅参考资料):清单2.AlarmClock类packageorg.tiling.scheduling.examples;importjava.text.SimpleDateFormat;importjava.util.Date;importorg.tiling.scheduling.Scheduler;importorg.tiling.scheduling.SchedulerTask;importorg.tiling.scheduling.examples.iterators.DailyIterator;publicclassAlarmClock{privatefinalSchedulerscheduler=newScheduler();privatefinalSimpleDateFormatdateFormat=newSimpleDateFormat(ddMMMyyyyHH:mm:ss.SSS);privatefinalinthourOfDay,minute,second;publicAlarmClock(inthourOfDay,intminute,intsecond){this.hourOfDay=hourOfDay;this.minute=minute;this.second=second;}publicvoidstart(){scheduler.schedule(newSchedulerTask(){publicvoidrun(){soundAlarm();}privatevoidsoundAlarm(){System.out.println(Wakeup!+It's+dateFormat.format(newDate()));//Startanewthreadtosoundanalarm...}},newDailyIterator(hourOfDay,minute,second));}publicstaticvoidmain(String[]args){AlarmClockalarmClock=newAlarmClock(7,0,0);alarmClock.start();}}注意这段代码与煮蛋计时器应用程序非常相似。AlarmClock实例拥有一个Scheduler(而不是Timer)实例,用于提供必要的计划。启动后,这个闹钟对SchedulerTask(而不是TimerTask)进行调度用以发出报警声。这个闹钟不是计划一个任务在固定的延迟时间后执行,而是用DailyIterator类描述其计划。在这里,它只是计划任务在每天上午7:00执行。下面是一个正常运行情况下的输出:Wakeup!It's24Aug200307:00:00.023Wakeup!It's25Aug200307:00:00.001Wakeup!It's26Aug200307:00:00.058Wakeup!It's27Aug200307:00:00.015Wakeup!It's28Aug200307:00:00.002...DailyIterator实现了ScheduleIterator,这是一个将SchedulerTask的计划执行时间指定为一系列java.util.Date对象的接口。然后next()方法按时间先后顺序迭代Date对象。返回值null会使任务取消(即它再也不会运行)――这样的话,试图再次计划将会抛出一个异常。清单3包含ScheduleIterator接口:清单3.ScheduleIterator接口packageorg.tiling.scheduling;importjava.util.Date;publicinterfaceScheduleIterator{publicDatenext();}DailyIterator的next()方法返回表示每天同一时间(上午7:00)的Date对象,如清单4所示。所以,如果对新构建的next()类调用next(),那么将会得到传递给构造函数的那个日期当天或者后面一天的7:00AM。再次调用next()会返回后一天的7:00AM,如此重复。为了实现这种行为,DailyIterator使用了java.util.Calendar实例。构造函数会在日历中加上一天,对日历的这种设置使得第一次调用next()会返回正确的Date。注意代码没有明确地提到夏令时修正,因为Calendar实现(在本例中是GregorianCalendar)负责对此进行处理,所以不需要这样做。清单4.DailyIterator类packageorg.tiling.scheduling.examples.iterators;importorg.tiling.scheduling.ScheduleIterator;importjava.util.Calendar;importjava.util.Date;/***ADailyIteratorclassreturnsasequenceofdatesonsubsequentdays*representingthesametimeeachday.*/publicclassDailyIteratorimplementsScheduleIterator{privatefinalinthourOfDay,minute,second;privatefinalCalendarcalendar=Calendar.getInstance();publicDailyIterator(inthourOfDay,intminute,intsecond){this(hourOfDay,minute,second,newDate());}publicDailyIterator(inthourOfDay,intminute,intsecond,Datedate){this.hourOfDay=hourOfDay;this.minute=minute;this.second=second;calendar.setTime(date);calendar.set(Calendar.HOUR_OF_DAY,hourOfDay);calendar.set(Calendar.MINUTE,minute);calendar.set(Calendar.SECOND,second);calendar.set(Calendar.MILLISECOND,0);if(!calendar.getTime().before(date)){calendar.add(Calendar.DATE,-1);}}publicDatenext(){calendar.add(Calendar.DATE,1);returncalendar.getTime();}}实现计划框架在上一节,我们学习了如何使用计划框架,并将它与Java定时器框架进行了比较。下面,我将向您展示如何实现这个框架。除了清单3中展示的ScheduleIterator接口,构成这个框架的还有另外两个类――Scheduler和SchedulerTask。这些类实际上在内部使用Timer和SchedulerTask,因为计划其实就是一系列的单次定时器。清单5和6显示了这两个类的源代码:清单5.Schedulerpackageorg.tiling.scheduling;importjava.util.Date;importjava.util.Timer;importjava.util.TimerTask;publicclassScheduler{classSchedulerTimerTaskextendsTimerTask{privateSchedule