在实际项目开发中,除了Web应用、SOA服务外,还有一类不可缺乏的,那就是定时任务调度。定时任务的场景能够说很是普遍,好比某些视频网站,购买会员后,天天会给会员送成长值,每个月会给会员送一些电影券;好比在保证最终一致性的场景中,每每利用定时任务调度进行一些比对工做;好比一些定时须要生成的报表、邮件;好比一些须要定时清理数据的任务等。本篇博客将系统的介绍定时任务调度,会涵盖Timer、ScheduledExecutorService、开源工具包Quartz,以及Spring和Quartz的结合等内容。java
定时任务调度:基于给定的时间点、给定的时间间隔、给定的执行次数自动执行的任务。编程
Timer位于java.util包下,其内部包含且仅包含一个后台线程(TimeThread)对多个业务任务(TimeTask)进行定时定频率的调度。并发
schedule的四种用法和scheduleAtFixedRate的两种用法框架
参数说明:分布式
task:所要执行的任务,须要extends TimeTask override run()ide
time/firstTime:首次执行任务的时间工具
period:周期性执行Task的时间间隔,单位是毫秒网站
delay:执行task任务前的延时时间,单位是毫秒ui
很显然,经过上述的描述,咱们能够实现:.net
延迟多久后执行一次任务;指定时间执行一次任务;延迟一段时间,并周期性执行任务;指定时间,并周期性执行任务;
思考1:若是time/firstTime指定的时间,在当前时间以前,会发生什么呢?
在时间等于或者超过time/firstTime的时候,会执行task!也就是说,若是time/firstTime指定的时间在当前时间以前,就会当即获得执行。
思考2:schedule和scheduleAtFixedRate有什么区别?
scheduleAtFixedRate:每次执行时间为上一次任务开始起向后推一个period间隔,也就是说下次执行时间相对于上一次任务开始的时间点,所以执行时间不会延后,可是存在任务并发执行的问题。
schedule:每次执行时间为上一次任务结束后推一个period间隔,也就是说下次执行时间相对于上一次任务结束的时间点,所以执行时间会不断延后。
思考3:若是执行task发生异常,是否会影响其余task的定时调度?
若是TimeTask抛出RuntimeException,那么Timer会中止全部任务的运行!
思考4:Timer的一些缺陷?
前面已经说起到Timer背后是一个单线程,所以Timer存在管理并发任务的缺陷:全部任务都是由同一个线程来调度,全部任务都是串行执行,意味着同一时间只能有一个任务获得执行,而前一个任务的延迟或者异常会影响到以后的任务。
其次,Timer的一些调度方式还算比较简单,没法适应实际项目中任务定时调度的复杂度。
一个简单的Demo实例
Timer其余须要关注的方法
cancel():终止Timer计时器,丢弃全部当前已安排的任务(TimeTask也存在cancel()方法,不过终止的是TimeTask)
purge():从计时器的任务队列中移除已取消的任务,并返回个数
因为Timer存在的问题,JDK5以后便提供了基于线程池的定时任务调度:ScheduledExecutorService。
设计理念:每个被调度的任务都会被线程池中的一个线程去执行,所以任务能够并发执行,并且相互之间不受影响。
咱们直接看例子:
虽然ScheduledExecutorService对Timer进行了线程池的改进,可是依然没法知足复杂的定时任务调度场景。所以OpenSymphony提供了强大的开源任务调度框架:Quartz。Quartz是纯Java实现,并且做为Spring的默认调度框架,因为Quartz的强大的调度功能、灵活的使用方式、还具备分布式集群能力,能够说Quartz出马,能够搞定一切定时任务调度!
Quartz的体系结构
先来看一个Demo:
说明:
一、从代码上来看,有XxxBuilder、XxxFactory,说明Quartz用到了Builder、Factory模式,还有很是易懂的链式编程风格。
二、Quartz有3个核心概念:调度器(Scheduler)、任务(Job&JobDetail)、触发器(Trigger)。(一个任务能够被多个触发器触发,一个触发器只能触发一个任务)
三、注意当Scheduler调度Job时,实际上会经过反射newInstance一个新的Job实例(待调度完毕后销毁掉),同时会把JobExecutionContext传递给Job的execute方法,Job实例经过JobExecutionContext访问到Quartz运行时的环境以及Job自己的明细数据。
四、JobDataMap能够装载任何能够序列化的数据,存取很方便。须要注意的是JobDetail和Trigger均可以各自关联上JobDataMap。JobDataMap除了能够经过上述代码获取外,还能够在YourJob实现类中,添加相应setter方法获取。
五、Trigger用来告诉Quartz调度程序何时执行,经常使用的触发器有2种:SimpleTrigger(相似于Timer)、CronTrigger(相似于Linux的Crontab)。
六、实际上,Quartz在进行调度器初始化的时候,会加载quartz.properties文件进行一些属性的设置,好比Quartz后台线程池的属性(threadCount)、做业存储设置等。它会先从工程中找,若是找不到那么就是用quartz.jar中的默认的quartz.properties文件。
七、Quartz存在监听器的概念,好比任务执行先后、任务的添加等,能够方便实现任务的监控。
CronTrigger示例
这里给出一些经常使用的示例:
0 15 10 ? * 天天10点15分触发
0 15 10 ? 2017 2017年天天10点15分触发
0 14 * ? 天天下午的 2点到2点59分每分触发
0 0/5 14 ? 天天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 ? 天天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 ? 天天下午的 2点到2点05分每分触发
0 15 10 ? * 6L 每个月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 每个月的第三周的星期五开始触发
咱们能够经过一些Cron在线工具很是方便的生成,好比http://www.pppet.net/等。
实际上,Quartz和Spring结合是很方便的,无非就是进行一些配置。大概基于2种方式:
第一,普通的类,普通的方法,直接在配置中指定(MethodInvokingJobDetailFactoryBean)。
第二,须要继承QuartzJobBean,复写指定方法(executeInternal)便可。
而后,就是一些触发器、调度器的配置了,这里再也不展开介绍了,只要弄懂了原生的Quartz的使用,那么和Spring的结合使用就会很简单。