任务定时调度在企业级开发中的研究
2015-04-25孙忠林
王 秀,孙忠林,姜 莉
(山东科技大学 信息科学与工程学院,山东 青岛266590)
在企业级应用开发中,为了提高系统的效率,需要进行基于给定时间点,给定时间间隔或者给定执行次数的任务。随着业务流程复杂性的提升,自动化流程的益处显现更加明显,如企业网站非法用户解锁功能,需要实现每30 min对非法IP解锁的需求,这些任务无需人机交互,只需在系统后台运行。任务调度能较好满足企业应用的需求,更好地实现自动化。在Java的应用开发有多种多样的方法提供定时任务调度。
本文从实现原理和程序设计的角度分析3种任务调度的方式,包括使用JDK Timer,使用基于ScheduledExecuter接口的实现和使用Quartz调度器,满足复杂多样的任务定时调度。JDK Timer适用于特定时间点执行或以固定周期运行的任务,ScheduledExecuter可并发的完成定时任务,而Quartz可根据调度策略执行复杂的调度需求[1]。
1 使用JDK Timer调度任务
1.1 JDK Timer的原理
实现Timer的核心是Tasklist和TaskThread,Timer将接收到的任务存放在TimerList中,TimerList按照Task的最初执行时间进行排序。TimerThread在创建Timer时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread被唤醒并执行该任务,之后TimerThread循环更新最近一个要执行的任务,继续休眠。
1.2 使用JDK Timer实现任务调度
创建一个TimerTask的继承类,实现自身的run方法,然后将其交给Timer去执行。
public class MyTask extends TimerTask{
Public void run(){…}}
Timer time=new Timer();
MyTask task=new MyTask();
time.schedule(task,0,2000)//每隔2 s执行一次
Timer的优点在于简单易用,但Timer对任务的调度基于绝对时间且是单线程执行,因此同一时刻只能执行一个任务。由于JDK Timer线程并不捕获异常,所以当TimerTask抛出未检查的异常,就会终止timer线程,已被安排尚未执行的任务和新的任务均无法继续执行下去。
2 实现类调度任务
2.1 ScheduledExecuter接口的原理
鉴于Timer的上述缺点,ScheduledExecuter是基于线程池实现的,每一个被调度的任务均会由线程池中一个线程去执行,任务是并发进行,相互之间不会受到干扰。
ScheduledExecuter的设计思想是提供一个统一的任务执行接口,通过execute方法可将任务放到调度队列中。ScheduledExecuter在任务来临前处于轮询任务状态,只有当任务被执行时,才会启动一个线程。
ScheduledExecuter整体结构如图1所示。
图1 ScheduledExecuter整体结构图
Exector接口定义了用于接受用户提交任务的execute方法。ExectorService继承Exector接口,用于定义线程的生命周期,包括线程的运行、关闭和终止状态。ScheduledExectorService在ExectorService基础上提供了按时间安排执行任务的功能并能延时一段时间触发。ThreadPoolExecutor提供线程池的核心实现,支持定时和周期性执行任务。文中可根据不同的需求来使用不同的接口。
由于spring对ThreadPoolExecutor提供了较好的支持,在企业应用中,一般使用spring提供的SchedulingTaskExector子接口来实现任务调度。该接口实现了SimpleSyncTaskExecutor、TimerTaskExecutor、Thread-PoolTaskExecutor等类[2-3]。使用时可通过配置注入的方式实现。
2.2 使用ScheduledExecuter实现任务调度
以下任务1 s后开始执行,每隔1 s执行任务1,从第2 s开始,每隔1 s执行任务2,从第3 s开始,每隔1 s执行任务3,从第4 s开始,每隔1 s执行任务4。
由执行结果来看,1 s后输出job1,2 s后同时输出job1,job2,3 s后同时输出job1,job2,job3,4 s后同时输出job1,job2,job3,job4。结果分析如图2所示。
图2 ScheduledExecuter结果分析图
Timer可实现简单的单线程定时任务,ScheduledExecutor可线程池的方式并发的执行任务调度,来弥补timer的不足,然而当遇到更复杂的任务,这种任务需要结构时间工具类,ScheduledExecutor就无法满足需求。而Quartz将定时程序做了较好的封装,来方便企业实现定时任务的调度需求。
3 使用Quartz调度器调度任务
3.1 Quartz调度器的原理
Quartz是个开源的作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制[4-5]。其实现了作业和触发器的多对多关系,还能将多个作业与不同的触发器关联。整合了Quartz的应用程序,可重用来自不同事件的任务,还可为一个事件组合多个任务[6]。
Quartz任务调度的核心元素是Scheduler,Trigger触发器和Job作业[7],其中trigger和job是任务调度的元数据,scheduler是实际执行调度的控制器。关系如图3所示。
图3 Quartz核心元素关系图
(1)trigger,用于定义调度时间的元素,即按照时间规则去执行任务。Quartz中主要提供了4种类型的trigger:SimpleTrigger、CronTirgger、DateIntervalTrigger和thIncludedDayTrigger。
(2)jobDetail,表示被调度的任务内容。这个接口只有一个方法exector().job和trigger如上图所示为一对多的关系。
(3)Scheduler是一个计划调度器容器(总部),容器内可容纳较多的JobDetail和Trigger,当容器启动后,里面的每个JobDetail均会根据trigger按部就班自动去执行。调度线程主要有两个,常规调度的线程和misfired trigger的线程。常规调度线程从任务执行线程池获取一个空闲线程,执行与该trigger关联的任务。Misfire线程是扫描所有的trigger,若有misfired trigger,则根据misfire的策略分别处理。scheduler是个容器,容器中有一个线程池,用来并行调度执行每个作业。
当任务调度执行时,Scheduler初始化启动,配置QuartzSchedulerThread,然后取出JobStore里要触发的Trigger,进入线程等待状态,直到其出发点来临[8]。之后执行trigger对应的JobDetail。任务调度执行完成。Scheduler初始化、start和trigger执行的时序图4所示。
图4 Quartz时序图
3.2 使用Quartz实现任务调度
在企业级任务调度中,Quartz通常与Spring整合来实现定时任务的调度。例如,在某单位会议管理系统中,需定时发送邮件,该功能为会议创建人创建会议后系统会在会议举行前半小时邮件告知与会人员参加会议[9-10]。具体实现如下:
4 结束语
本文介绍了3种常用的对任务进行调度的Java实现方法,即JDKTimer,ScheduledExecutor接口的实现类,Quartz调度器。对于简单的基于起始时间点与时间间隔的任务调度,可使用JDK Timer;若需要同时调度多个任务,基于线程池的ScheduledExecuter是更为合适的选择;当任务调度的策略复杂到难以凭借起始时间点与时间间隔来描述时,Quartz调度器则体现出其的优势。在实际企业任务调度中,根据实际情况选择,以便更好、更快的提高任务调度速度与效率。
[1] 胡利强,周冬初,王伟.Quartz调度器与Web程序整合的研究和应用[J].计算机与现化,2010(8):98-99.
[2] 丁振凡,李馨梅.Spring的任务调度方法研究[J].智能计算机与应用,2012(8):54-60.
[3]CraigWalls.Spring in action[M].2版.毕庆红,译.北京:人民邮电出版社,2008.
[4] 刘博仁.利用Quartz框架实现作业调度的解决方案[J].计算机光盘软件与应用,2010(9):160-161.
[5]Bruce Eckel.Thinking in Java[M].北京:机械工业出版社,2007.
[6] 结城浩.Java多线程设计模式[M].北京:中国铁道出版社,2005.
[7] 王崟,董志勇.基于Quartz的网管系统任务调度的实现[J].电脑开发与应用,2011(24):23-25.
[8] 朱哲明.基于Quartz的消息平台的研究[D].北京:北京邮电大学,2013.
[9] 陈雄华,林开熊.Spring3.x企业应用开发实战[M].北京:电子工业出版社,2012.
[10]Craig Walls,Ryan Breidenbach.Thinking in spring[M].2版.毕庆红,译.北京:人民邮电出版社,2008.