一、简介
Quartz是一个作业调度的框架,可以让程序按照设定的日期和时间来执行。简而言之就是按照你设置的时间来定时执行任务。
二、核心模块
主要的核心模块有Job/JobDetial/Trigger/Calendar/Scheduler,下面来分别进行介绍:
- Job
是一个接口,只存在一个方法execute,需要写出自己的Job相关类来实现此接口,而execute的方法就是需要执行的任务,例如下面的例子:
- JobDetial
JobDetail表示一个具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容,通过反射机制来创建Job实例,另外JobDetail还包含了这个任务调度的方案和策略。,如下图
name、group分别可设置任务名和组名
- Trigger
主要设置任务具体什么时间去执行,比如定时没五分钟执行一次、每天10点执行一次等:
上图中设置了triger的名字、组名、调度的任务,以及调度规则(使用withSchedule(CronScheduleBuilder.cronSchedule(规则表达式))进行设置),这里重点在于规则表达式,其规则为:
各个统配代表的含义如下:
“*” 表示所有值,例如:在分的字段上设置*,则表示每分钟都会触发
“?” 表示指定值,使用场景是不关心设置了“?”的字段,比如在每月的10号执行任务,不关心是周几,所以在周的位置上设置?,具体设置为0 0 0 10 * ?
“-”表示区间,例如在小时上设置10-12,则表示10点、11点、12点都会触发
“,” 表示指定多个值,例如在周字段上加“MON,WED,FRI”表示每周一、周三、周五触发
“/”用于递增触发,如在秒上设置“5/15”,表示从第5秒开始,每15秒触发一次
“L”表示最后的意思,在日字段上设置,表示当月最后一天
“W”表示离指定日期最近的那个工作日,例如在日字段上设置“15W”,表示离每预测15号最近的那个工作日触发
常用示例:
详细讲一下调度规则,trigger主要分两类sippleTrigger和CronTrigger
sippleTrigger:
在一个指定时间段内执行一次作业任务或在指定时间间隔内执行多次作业任务
例子: 4秒钟后执行任务并且只执行一次:
例子2:4秒后执行,以后每隔两秒执行一次:
类似这样的调度规则只需要调用相应的方法进行设置即可
CronTrigger:
相比于SimpleTriger更具有具体实际意义的调度规则,基于Cron表达式,上面介绍的例子就是使用的CronTrigger,更加常用,只需要设置乡音搞得cron表达式即可
例子:
- Scheduler
一个任务的调度器,可以把上面说到的任务jobDetil和Trigger注册到调度器中,就可运行调度程序,一个调度器可以注册多个jobDetial和Trigger,例如:
注册进调度器的JobDetial和Trigger有多个,调度器可以通过每个JobDetial和Trigger的名字和组名取获取对应的JobDetial和Trigger对象。
一个Job可以对应更多个trigger,但是一个trigger只能对应一个Job,调度器Scheduler有一上下文SchedulerContext(类似于servletContext),内部通过一个map存放信息,Jobdetail和Trigger都可以访问这些信息,这一点放到后面讲解。
- Calender
这里指的是org.quartz.Calendar,主要作用在于一个Trigger可以和多个calendar进行关联,用于排除或包含某些节点,比如设定的是每周的13点执行某项任务,但是遇到法定节假日不执行,这是后就要在triiger加入calendar,排除法定节日,calendar有很多实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别是针对每年、每月、每周进行设置,来看一个小例子:
下面的例子是每2秒钟执行一次任务,但排除劳动节,国庆节:
三、JobDataMap和SchedulerContext与任务间的数据共享
JobDataMap中存储了一些数据对象,使你在执行job实例的时候使用这些对象,这些对象在JobDetial中设置存储,并通常在Job任务类中传入的JobExecutionContext获取,来看下面这个简单例子:
在创建JobDetial中放入对象:
或者:
然后在MyJob.calss中获取这些对象:
原则上JobDataMap里面可以存任何类型的对象,上面的例子只是简单存了String类型的对象。但是如果你使用了Quartz的持久化功能,对于JobDataMap中的基本类型是可以存入数据库的,但是其他的类型存入数据库是以Blob类型存储的,这就要求JobDataMap存入的非基本类型的对象必须实现序列化。
JobDataMap主要的应用场景在与执行Job.class时,可能要接收从外部传进来的参数,所以需要在job外部的JobDetial中写进参数,从内部获取。
SchedulerContext:
Scheduler的上下文,主要维护Scheduler中信息,可以通过相应的get和put方法获取上下文中的对象,例如我们在上下文中个存入数据库连接对象,然后在job中进行获取:
scheduler中存入上下文对象
在Job中获取对象:
数据共享问题
- 同一个Jobdetail中多个实例共享数据
因为在每次执行的时候,都会由JobDetial创建一个新的Job实例进行执行,所以这里存在连个问题:
- 并发问题;假设定时任务的时间间隔为3秒,但是执行任务所需要的时间是10秒,默认的执行任务所需要的时间是不影响任务执行时间的,也就是说仍然会三秒执行一次,这就会造成统一时间,有多个相同的任务在执行。如果想要达到等待程序执行完任务后,才开启下一次的执行,则需要在实现的Job类上加上注解@DisallowConcurrentExcution,如下所示:
- 数据共享问题:
JobDetial中的任务类型分为有状态和无状态;
无状态任务是指任务拥有JobDtial的JobDataMap的拷贝,使用时拷贝出来一份供自己使用,对JobDataMap的更改不会影响下次的执行;
而有状态任务共享同一个JobDataMap,每次对JobDataMap所做的修改都会保存下来,会对后面执行的任务产生影响。
Quartz默认的Job是无状态,若果是有状态的,需要实现的Job类上加上@PersistJobDataAfterExecution,如下所示:
一般如果要定义Job是又装台的,除了@PersistJobDataAfterExecution外,还需加上@DisallowConcurrentExcution,避免并发问题。(即必须在此刻的线程执行完后,JoBDataMap修改完后,在执行下一个任务,保证任务串行执行,这样JoBDataMap中变量的修改才有意义)
- 不同DetialJob的数据共享
需求是,两个不同定时任务,需要共享数据,简单的小例子,例如一个定时任务负责将count加1,另一个定时任务负责展示count,如果数据能共享,那么展示任务每次展示的count会随着增加任务的执行而递增。由于涉及到不同JobDetial,所以JobDataMap无法实现,因为JobDataMap是针对同一个DetialJob的,而我们这里是不同的DetialJob。
所以这里我们考虑使用scheduler的上下文context实现。
在scheduler中定义一个count,并将此加入context上下文中,定时任务job1中每2秒从上下文获取count并加1,定时任务job2中每1秒从上下文count并展示;
首先定义Schedular:
接着是两个Job:MyJob用于获取count并加1,MyJob1用于获取count进行展示:
执行后查看结果:
结果已经很明显,这样的方式可以用与不同DetialJob执行时之间的数据共享