JAVA定时任务系列(三、定时任务框架Quartz)
接上文......
五、定时任务框架Quartz
(1) 介绍Quartz
Quartz
框架是Java领域最著名的开源任务调度工具,也是目前事实上的定时任务标准,几乎全部的开源定时任务框架都是基于Quartz核心调度构建而成。
(2) Quartz 框架的特点
(2.1) Quartz 优点
作为一个优秀的开源调度框架,Quartz
具有以下优点:
强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。本文暂不讨论该部分内容
另外,作为 Spring 默认的调度框架,Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。
(2.1) Quartz 缺点
不适合大量的短任务,不适合过多节点部署
需要把任务信息持久化到业务数据表,和业务有耦合;
调度逻辑和执行逻辑并存于同一个项目中,在机器性能固定的情况下,业务和调度之间不可避免地会相互影响;
quartz集群模式下,是通过数据库独占锁来唯一获取任务,任务执行并没有实现完善的负载均衡机制;
(3) Quartz 核心组件
核心组件图和架构:
关键概念:
(1) Scheduler
:任务调度器,是执行任务调度的控制器。本质上是一个计划调度容器,注册了全部Trigger
和对应的JobDetail
, 使用线程池作为任务运行的基础组件,提高任务执行效率。
(2) Trigger
:触发器,用于定义任务调度的时间规则,告诉任务调度器什么时候触发任务,其中CronTrigger
是基于cron
表达式构建的功能强大的触发器。
(3) Calendar
:日历特定时间点的集合。一个trigger
可以包含多个Calendar
,可用于排除或包含某些时间点。
(4) JobDetail
:是一个可执行的工作,用来描述Job
实现类及其它相关的静态信息,如Job
的名称、监听器等相关信息。
(5) Job
:任务执行接口,只有一个execute
方法,用于执行真正的业务逻辑。
(6) JobStore
:任务存储方式,主要有RAMJobStore
和JDBCJobStore
,RAMJobStore
是存储在JVM
的内存中,有丢失和数量受限的风险,JDBCJobStore
是将任务信息持久化到数据库中,支持集群。
(3) Quartz 框架使用推荐
(1)业务使用要满足动态修改和重启不丢失, 一般需要使用数据库进行保存。
Quartz
本身支持JDBCJobStore
,但是其配置的数据表比较多,官方推荐配置可参照官方文档,超过10张表,业务使用比较重。
在使用的时候只需要存在基本
trigger
配置和对应任务以及相关执行日志的表即可满足绝大部分需求。
(2)组件化
将
quartz
动态任务配置信息持久化到数据库,将数据操作包装成基本jar包,供项目之间使用,引用项目只需要引入jar
包依赖和配置对应的数据表,使用时就可以对Quartz
配置透明。
(3)扩展
集群模式
通过故障转移和负载均衡实现了任务的高可用性,通过数据库的锁机制来确保任务执行的唯一性,但是集群特性仅仅只是用来HA,节点数量的增加并不会提升单个任务的执行效率,不能实现水平扩展。
Quartz插件*
可以对特定需要进行扩展,比如增加触发器和任务执行日志,任务依赖串行处理场景,可参考quartz插件——实现任务之间的串行调度
(4)Quartz 任务调度的基本实现原理
(4.1) 核心元素
\(~~~~~~\) Quartz
任务调度的核心元素是 scheduler
(核心调度器), trigger
和 job
,其中 trigger
和 job
是任务调度的元数据, scheduler 是实际执行调度的控制器。
\(~~~~~~\) 在 Quartz
中,trigger
是用于定义调度时间的元素,即按照什么时间规则去执行任务。Quartz 中主要提供了四种类型的 trigger
:SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIcludedDayTrigger。这四种 trigger
可以满足企业应用中的绝大部分需求。进一步讨论四种 trigger
的功能。
\(~~~~~~\) 在 Quartz
中,job
用于表示被调度的任务。主要有两种类型的job
:无状态的(stateless
)和有状态的(stateful
)。对于同一个 trigger
来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。Job
主要有两种属性:volatility
和 durability
,其中** volatility
表示任务是否被持久化到数据库存储,而 durability
表示在没有 trigger
关联的时候任务是否被保留。两者都是在值为true
的时候任务被持久化或保留
。一个 job
可以被多个 trigger
关联,但是一个 trigger
只能关联一个 job
。**
\(~~~~~~\)在Quartz
中, scheduler
由 scheduler 工厂创建
:DirectSchedulerFactory
或者 StdSchedulerFactory
。 第二种工厂 StdSchedulerFactory
使用较多,因为 DirectSchedulerFactory
使用起来不够方便,需要作许多详细的手工编码设置。 Scheduler
主要有三种:RemoteMBeanScheduler
, RemoteScheduler
和 StdScheduler
。本文以最常用的 StdScheduler
为例讲解。这也是笔者在项目中所使用的 scheduler
类。
(4.2) Quartz 核心元素关系图
(4.3) Quartz 线程视图和调度流程图
在 Quartz 中,有两类线程,Scheduler 调度线程和任务执行线程,其中任务执行线程通常使用一个线程池维护一组线程。
Scheduler
调度线程主要有两个:** 执行常规调度的线程,和执行misfired trigger
的线程。常规调度线程轮询存储的所有trigger
,如果有需要触发的trigger
,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该trigger
关联的任务。Misfire
线程是扫描所有的trigger
,查看是否有 misfired trigger**,如果有的话根据misfire
的策略分别处理。下图描述了这两个线程的基本流程:
(4.4) 数据存储
Quartz
中的trigger
和 job
需要存储下来才能被使用。Quartz
中有两种存储方式:RAMJobStore
, JobStoreSupport
,其中 RAMJobStore
是将trigger
和job
存储在内存中,而 JobStoreSupport
是基于 jdbc
将 trigger
和 job
存储到数据库中。RAMJobStore
的存取速度非常快,但是由于其在系统被停止后所有的数据都会丢失,所以在通常应用中,都是使用 JobStoreSupport
。
在 Quartz
中,JobStoreSupport
使用一个驱动代理来操作 trigger
和 job
的数据存储:StdJDBCDelegate
。StdJDBCDelegate
实现了大部分基于标准 JDBC
的功能接口,但是对于各种数据库来说,需要根据其具体实现的特点做某些特殊处理,因此各种数据库需要扩展 StdJDBCDelegate 以实现这些特殊处理。 Quartz
已经自带了一些数据库的扩展实现,可以直接使用,如下图所示:
作为嵌入式数据库的代表,Derby
近来非常流行。如果使用 Derby
数据库,可以使用上图中的 CloudscapeDelegate
作为 trigger
和 job
数据存储的代理类。
(5)Quartz 开发过程中的应用
(5.1) 如何使用不同类型的 Trigger
前面我们提到Quartz
中四种类型的 Trigger:SimpleTrigger
,CronTirgger
,DateIntervalTrigger
, 和 NthIncludedDayTrigger
。
-
SimpleTrigger
: 一般用于实现每隔一定时间执行任务,以及重复多少次,如每 2 小时执行一次,重复执行 5 次
SimpleTrigger
内部实现机制是通过计算间隔时间来计算下次的执行时间,这就导致其不适合调度定时的任务。例如我们想每天的 1:00AM 执行任务,如果使用 SimpleTrigger 的话间隔时间就是一天。注意这里就会有一个问题,即当有 misfired 的任务并且恢复执行时,该执行时间是随机的(取决于何时执行 misfired 的任务,例如某天的 3:00PM)。这会导致之后每天的执行时间都会变成 3:00PM,而不是我们原来期望的 1:00AM。 -
CronTirgger
类似于LINUX
上的任务调度命令crontab
,即利用一个包含 7 个字段的表达式来表示时间调度方式。例如,"0 15 10 * * ? *" 表示每天的 10:15AM 执行任务。对于涉及到星期和月份的调度,CronTirgger
是最适合的,甚至某些情况下是唯一选择。例如,"0 10 14 ? 3 WED" 表示三月份的每个星期三的下午 14:10PM 执行任务。读者可以在具体用到该 trigger 时再详细了解每个字段的含义。 -
DateIntervalTrigger
是Quartz 1.7
之后的版本加入的,其最适合调度类似每 N(1, 2, 3...)小时,每 N 天,每 N 周等的任务。虽然 SimpleTrigger 也能实现类似的任务,但是 DateIntervalTrigger 不会受到我们上面说到的 misfired 任务的影响。另外,DateIntervalTrigger 也不会受到 DST(Daylight Saving Time, 即中国的夏令时)调整的影响。笔者就曾经因为该原因将项目中的 SimpleTrigger 改为了 DateIntervalTrigger,因为如果使用 SimpleTrigger,本来设定的调度时间就会由于 DST 的调整而提前或延迟一个小时,而 DateIntervalTrigger 不会受此影响。 -
NthIncludedDayTrigger
的用途比较简单明确,即用于每隔一个周期的第几天调度任务,例如,每个月的第 3 天执行指定的任务。
除了上面提到的 4 种 Trigger
,Quartz
中还定义了一个 Calendar 类
(注意,是 org.quartz.Calendar
)。这个 Calendar
与 Trigger
一起使用,但是它们的作用相反,它是用于排除任务不被执行的情况。例如,按照 Trigger 的规则在 10 月 1 号需要执行任务,但是 Calendar 指定了 10 月 1 号是节日(国庆),所以任务在这一天将不会被执行。通常来说,Calendar
用于排除节假日的任务调度,从而使任务只在工作日执行。
(5.2) 使用有状态(StatefulJob)还是无状态的任务(Job)
在 Quartz
中,Job
是一个接口,企业应用需要实现这个接口以定义自己的任务。基本来说,任务分为有状态和无状态两种。实现 Job
接口的任务缺省为无状态的。Quartz
中还有另外一个接口 StatefulJob
。实现 StatefulJob 接口的任务为有状态的,上一节的简单实例中,我们定义的 SampleJob 就是实现了 StatefulJob 接口的有状态任务。下图列出了 Quartz 中 Job 接口的定义以及一些自带的实现类:
无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰。例如我们定义一个 trigger
,每 2 分钟执行一次,但是某些情况下一个任务可能需要 3 分钟才能执行完,这样,在上一个任务还处在执行状态时,下一次触发时间已经到了。对于无状态任务,只要触发时间到了就会被执行,因为几个相同任务可以并发执行。但是对有状态任务来说,是不能并发执行的,同一时间只能有一个任务在执行。
在笔者项目中,某些任务需要对数据库中的数据进行增删改处理。这些任务不能并发执行,否则会造成数据混乱。因此我们使用 StatefulJob
接口。现在回到上面的例子,任务每 2 分钟执行一次,若某次任务执行了 5 分钟才完成,Quartz 会怎么处理呢?按照 trigger
的规则,第 2 分钟和第 4 分钟分别会有一次预定的触发执行,但是由于是有状态任务,因此实际不会被触发。在第 5 分钟第一次任务执行完毕时,Quartz
会把第 2 和第 4 分钟的两次触发作为 misfired job
进行处理。对于 misfired job
,Quartz
会查看其 misfire
策略是如何设定的,如果是立刻执行,则会马上启动一次执行,如果是等待下次执行,则会忽略错过的任务,而等待下次(即第 6 分钟)触发执行。
(5.3) 如何设置 Quartz 的线程池和并发任务
Quartz
中自带了一个线程池
的实现:SimpleThreadPool
。类如其名,这只是线程池的一个简单实现,没有提供动态自发调整等高级特性。Quartz
提供了一个配置参数:org.quartz.threadPool.threadCount
,可以在初始化时设定线程池的线程数量,但是一次设定后不能再修改。假定这个数目是 10,则在并发任务达到 10 个以后,再有触发的任务就无法被执行了,只能等待有空闲线程的时候才能得到执行。因此有些 trigger
就可能被 misfire
。但是必须指出一点,这个初始线程数并不是越大越好。当并发线程太多时,系统整体性能反而会下降,因为系统把很多时间花在了线程调度上。根据一般经验,这个值在 10 -- 50 比较合适。
对于一些注重性能的线程池来说,会根据实际线程使用情况进行动态调整,例如初始线程数,最大线程数,空闲线程数等。读者在应用中,如果有更好的线程池,则可以在配置文件中通过下面参数替换
SimpleThreadPool:org.quartz.threadPool.class = myapp.GreatThreadPool
(5.4) 如何处理 Misfired 任务
在 Quartz
应用中,misfired job
是经常遇到的情况。一般来说,下面这些原因可能造成 misfired job
:
系统因为某些原因被重启。在系统关闭到重新启动之间的一段时间里,可能有些任务会被
misfire
;
Trigger
被暂停(suspend
)的一段时间里,有些任务可能会被misfire
;
线程池中所有线程都被占用,导致任务无法被触发执行,造成
misfire
;
有状态任务在下次触发时间到达时,上次执行还没有结束;
为了处理 misfired job,Quartz
中为trigger
定义了处理策略,主要有下面两种:
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
:针对 misfired job
马上执行一次;
MISFIRE_INSTRUCTION_DO_NOTHING
:忽略 misfired job
,等待下次触发;
建议读者在应用开发中,将该设置作为可配置选项,使得用户可以在使用过程中,针对已经添加的 tirgger
动态配置该选项。
(5.5) 如何保留已经结束的 Trigger
在Quartz
中,一个tirgger
在最后一次触发完成之后,会被自动删除。Quartz
默认不会保留已经结束的 trigger
,如下面 Quartz
源代码所示:
但是在实际应用中,有些用户需要保留以前的 trigger
,作为历史记录,或者作为以后创建其他 trigger
的依据。如何保留结束的 trigger
呢?
一个办法是应用开发者自己维护一份数据备份记录,并且与 Quartz
原表的记录保持一定的同步。这个办法实际操作起来比较繁琐,而且容易出错,不推荐使用。
另外一个办法是通过修改并重新编译Quartz
的 trigger
类,修改其默认的行为。我们以 org.quartz.SimpleTrigger
为例,修改上面代码中 if (!mayFireAgain())
部分的代码如下:
另外我们需要在 SimpleTrigger
中定义一个新的类属性:needRetain
,如下所示:
在定义自己的 trigger
时,设置该属性,就可以选择是否在 trigger
结束时删除 trigger
。如下代码所示:
有人可能会考虑通过定义一个新的类,然后继承 org.quartz.SimpleTrigger
类并覆盖 executionComplete( )
方法来实现。但是这种方法是行不通的,因为 Quartz
内部在处理时会根据 trigger
的类型重新生成 SimpleTrigger
类的实例,而不是使用我们自己定义的类创建的实例。这一点应该是 Quartz
的一个小小的不足之处,因为它把扩展 trigger
的能力堵死了。好在Quartz
是开源的,我们可以根据需要进行修改。
(6)Quartz 框架,简单应用
(6.1) Quartz 应用场景
- 餐厅系统会在每周四晚上的22点自动审核并生成报表
- 人事系统会在每天早晨8点给有待办的人员自动发送Email提醒
(6.2) 简单使用(重复执行)
(6.2.1) 引入依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
(6.2.2) 创建HelloJob实现Job接口
public class HelloJob implements Job{
private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Date now = new Date();
String currentTime = sdf.format(now);
System.out.println("执行时间为:"+currentTime);
}
}
(6.2.3) 创建HelloScheduler触发任务
public class HelloScheduler {
public static void main(String[] args) throws SchedulerException {
//创建jobDetail绑定HelloJob
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob","myGroup").build();
//创建触发器trigger每个2秒执行一次,一直执行
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("mtTrigger", "myGroup").startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever()).build();
//创建调度者工厂
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
//创建调度者
Scheduler scheduler = schedulerFactory.getScheduler();
//启动调度器
scheduler.start();
//设置调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
}
执行结果:
执行时间为:2019-04-15 23:45:41
23:45:43.388 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.xuxu.quartz.HelloJob
23:45:43.388 [DefaultQuartzScheduler_Worker-9] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
23:45:43.388 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
执行时间为:2019-04-15 23:45:43
23:45:45.387 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.xuxu.quartz.HelloJob
23:45:45.387 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
23:45:45.391 [DefaultQuartzScheduler_Worker-10] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
执行时间为:2019-04-15 23:45:45
(6.3) 定时执行使用cron表达式确定时间
(6.3.1) 引入依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
(6.3.2)创建HelloJob实现job接口,任务执行时输出时间
public class HelloJob implements Job{
private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
Date now = new Date();
String currentDate = sdf.format(now);
System.out.println("现在时间是:"+currentDate+":开始执行任务生成表格,或者发送邮件");
}
}
(6.2.3) 创建触发类CronScheduler
public class CronScheduler {
public static void main(String[] args) throws Exception {
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob").build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger")
//cron表达式 这里定义的是 在每天下午2点到下午2:59期间的每1分钟触发
.withSchedule(CronScheduleBuilder.cronSchedule("0 * 14 * * ?"))
.build();
SchedulerFactory factory = new StdSchedulerFactory();
//创建调度器
Scheduler scheduler = factory.getScheduler();
//启动调度器
scheduler.start();
//jobDetail和trigger加入调度
scheduler.scheduleJob(jobDetail, trigger);
}
}
这里通过cron表达式确定时间规则
一般我们会使用cron生成器
执行结果如下:
现在时间是:2019-04-16 09:21:00:开始执行任务生成表格,或者发送邮件
(7) Quartz的三大API
(7.1) Job
JobDetail & Job & JobDataMap
JobDetail
是任务的定义,而Job是任务的执行逻辑。在JobDetail
里会引用一个Job Class
定义。每一个JobDetail
都会有一个JobDataMap
。JobDataMap
本质就是一个Map
的扩展类,只是提供了一些更便捷的方法,比如getString()
之类的。
public class CronScheduler {
public static void main(String[] args) throws Exception {
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
//添加jobname,jobgroup
.withIdentity("myJob","myGroup")
//jobDataMap信息
.usingJobData("message","this is a message")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger")
//cron表达式 这里定义的是4月16日早上9点21分开始执行
.withSchedule(CronScheduleBuilder.cronSchedule("0 00 10 16 4 ? *"))
.build();
SchedulerFactory factory = new StdSchedulerFactory();
//创建调度器
Scheduler scheduler = factory.getScheduler();
//启动调度器
scheduler.start();
//jobDetail和trigger加入调度
scheduler.scheduleJob(jobDetail, trigger);
}
}
public class HelloJob implements Job{
private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date now = new Date();
String currentDate = sdf.format(now);
JobDetail jobDetail = jobExecutionContext.getJobDetail();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
JobKey jobKey = jobDetail.getKey();
String jobName = jobKey.getName();
String group = jobKey.getGroup();
String message = (String) jobDataMap.get("message");
System.out.println("现在时间是:"+currentDate+":开始执行任务生成表格,或者发送邮件");
System.out.println("jobName---"+jobName);
System.out.println("group---"+group);
System.out.println("message---"+message);
}
}
现在时间是:2019-04-16 10:00:00:开始执行任务生成表格,或者发送邮件
jobName---myJob
group---myGroup
message---this is a message
(7.2) Tigger
(7.2.1)startTime和endTime
有时候我们希望一个定时任务在一定的时间内是每天执行,比如2017年11月24日到2017年12月15日之间执行,这时候我们就要使用startTime
和endTime
来限定事件范围了。例子中我们把时间规定在几秒钟之内运行,方便查看效果。
public class HelloJob implements Job{
private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Date now = new Date();
String currentTime = sdf.format(now);
System.out.println("执行时间为:"+currentTime);
}
}
public class HelloScheduler {
public static void main(String[] args) throws SchedulerException {
//创建jobDetail绑定HelloJob
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob","myGroup").build();
//设定开始时间,结束时间确定范围
Date triggerStartTime = new Date();
//3秒后开始执行
triggerStartTime.setTime(triggerStartTime.getTime()+3000);
Date triggerEndTime = new Date();
//10秒后结束执行
triggerEndTime.setTime(triggerEndTime.getTime()+10000);
//创建触发器trigger每个2秒执行一次,一直执行
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("mtTrigger", "myGroup")
.startAt(triggerStartTime)
.endAt(triggerEndTime)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever()).build();
//创建调度者工厂
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
//创建调度者
Scheduler scheduler = schedulerFactory.getScheduler();
//启动调度器
scheduler.start();
//设置调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
}
3秒后执行,10秒内结束执行
执行时间为:2019-04-16 10:36:09
执行时间为:2019-04-16 10:36:11
执行时间为:2019-04-16 10:36:13
执行时间为:2019-04-16 10:36:15
(7.2.2)BaseCalndar
此calendar
不是java.util.Calendar
,calendar
是为了补充Trigger
的时间,可以排除或加入一下特定的时间。Quartz
的 Calender
专门用于屏闭一个时间区间,使 Trigger
在这个区间中不被触发。
AnnualCalendar
:排除每一年中指定的一天或者多少天 ,精度是天CronCalendar
:使用表达式排除某些时间段不执行,精度取决于Cron
表达式,最大精度到秒DailyCalendar
:指定的时间范围内的每一天不执行,指定每天的时间段,格式是HH:MM[:SS[:mmm]]
。也就是最大精度可以到毫秒。HolidayCalendar
:排除节假日,精度到天MonthlyCalendar
:排除月份中的数天,可选值为1-31。精度是天WeeklyCalendar
:排除星期中的一天或多天,可选值比如为java.util.Calendar.SUNDAY
,精度是天。
这里使用CronCalendar排除
public class HelloJob implements Job{
private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Date now = new Date();
String currentTime = sdf.format(now);
System.out.println("执行时间为:"+currentTime);
}
}
public class HelloScheduler {
public static void main(String[] args) throws SchedulerException, ParseException {
//创建jobDetail绑定HelloJob
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob","myGroup").build();
//创建触发器trigger每个2秒执行一次,一直执行
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("mtTrigger", "myGroup")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever())
//将calendar排除规则绑定到触发器
.modifiedByCalendar("myCalendar")
.build();
//创建调度者工厂
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
//创建调度者
Scheduler scheduler = schedulerFactory.getScheduler();
CronCalendar calendar = new CronCalendar("* * 0-12,18-23 ? * *");
//向Scheduler注册日历
scheduler.addCalendar("myCalendar", calendar, false, false);
//启动调度器
scheduler.start();
//设置调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
}
11:39:12.270 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
11:39:12.381 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
上面指定的是0-12 18-23不执行发现12点之前没有执行
(7.3) Trigger的实现类
(7.3.1) CalendarIntervalTrigger
CalendarIntervalTrigger
:是一个具体的Trigger,用来触发基于定时重复的JobDetail。
Trigger将会每隔N个calendar在trigger中定义的时间单元触发一次。这个trigger不适合使用SimpleTrigger完成(例如由于每一个月的时间不是固定的描述),也不适用于CronTrigger(例如每5个月)。
相较于SimpleTrigger有两个优势:1、更方便,比如每隔1小时执行,你不用自己去计算1小时等于多少毫秒。 2、支持不是固定长度的间隔,比如间隔为月和年。但劣势是精度只能到秒
它适合的任务类似于:9:00 开始执行,并且以后每周 9:00 执行一次
它的属性有:
- interval 执行间隔
- intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)
CalendarIntervalScheduleBuilder
.calendarIntervalSchedule()
.withIntervalInDays(1) //每天执行一次
//.withIntervalInHours(1)
//.withIntervalInMinutes(1)
//.withIntervalInMonths(1)
//.withIntervalInSeconds(1)
//.withIntervalInWeeks(1)
//.withIntervalInHours(1)
.build()
(7.3.2) DailyTimeIntervalTrigger
指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。
它适合的任务类似于:指定每天9:00 至 18:00 ,每隔70秒执行一次,并且只要周一至周五执行。
它的属性有:
- startTimeOfDay 每天开始时间
- endTimeOfDay 每天结束时间
- daysOfWeek 需要执行的星期
- interval 执行间隔
- intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)
- repeatCount 重复次数
public static void main(String[] args) throws SchedulerException {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//1.创建一个jobDetail的实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder
.newJob(HelloJob.class)
.withIdentity("myJob", "group1") //定义name 和 group
.build();
//2.创建一个Trigger触发器的实例
Trigger simpleTrigger = TriggerBuilder.newTrigger()
.withIdentity("zhlTrigger")
.withSchedule(
DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule()
.startingDailyAt(TimeOfDay.hourAndMinuteOfDay(8, 0)) //每天8:00开始
.endingDailyAt(TimeOfDay.hourAndMinuteOfDay(17, 0)) //17:00 结束
.onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一至周五执行
.withIntervalInHours(1) //每间隔1小时执行一次
.withRepeatCount(100) //最多重复100次(实际执行100+1次)
)
.modifiedByCalendar("holidays") //将我们设置好的Calander与trigger绑定
.build();
//3.创建schedule实例
StdSchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
System.out.println("现在的时间 :"+sf.format(new Date()));
System.out.println();
System.out.println("最近的一次执行时间 :"+sf.format(scheduler.scheduleJob(jobDetail,simpleTrigger))); //scheduler与jobDetail、trigger绑定,并打印出最近一次执行的事件
scheduler.start();
}
(8) Scheduler工厂模式
所有的Scheduler
实例应该由SchedulerFactory
来创建,一般包含:StdSchedulerFactory、DirectSchedulerFactory
(参数信息需要在代码中维护故不常用)。
StdSchedulerFactory
使用一组参数来创建和初始化Quartz
调度器,配置参数一般存储在quartz.properties
文件中,调用getScheduler
方法就能创建和初始化调度器对象。
Scheduler的主要函数:
- Data scheduleJob(JobDetail jobDetail,Trigger trigger);
- void start();——启动Scheduler;
- void standby();——将Scheduler暂时挂起,可以用start()继续执行任务;
- void shutDown()关闭Scheduler且不能被重启
JAVA定时任务系列
JAVA定时任务系列(一、基于注解,基于接口)
JAVA定时任务系列(二、JDK原生定时工具:Timer)
JAVA定时任务系列(三、定时任务框架Quartz)