Java中实现定时任务的几种方式:
- Timer: java.util.Timer, 一个JDK自带的处理简单的定时任务的工具
- ScheduledExecutorService: java.util.concurrent.ScheduledExecutorService, JDK中的定时任务接口,可以将定时任务与线程池结合使用
- Sceduled: org.springframework.scheduling.annotation.Scheduled, Spring框架中基于注解来实现定时任务处理方式
- Quartz: 支持分布式调度任务的开源框架
Quartz:
- Quartz是功能强大的开源作业调度库,可以集成到最小的独立应用程序到最大的电子商务程序中
- Quartz可以通过创建简单或者复杂的计划来执行成千上万的任务
- 任何任务标准的Java组件,都可以执行相应的编程操作
- Quartz Scheduler支持JTA事务和集群
- Quartz实现了任务和触发器多对多的关系,可以将多个任务和不同的触发器相关联
使用场景
-
某个时间点执行的任务,或者每隔一段时间重复执行的任务
-
保持任务调度的定时状态的持久性任务
-
对调度任务进行有效管理的任务调度
-
示例:
- 自动关闭30分钟未支付的订单
- 定时与第三方公司对账业务,对接接口等
- 数据统计,比如博客系统统计日粉丝数,日阅读量等
- 活动开始通知和活动结束通知
- 在每个月25号自动还款的设置
- 每周或者每个月的提醒的事项
Scheduler
-
任务调度器Scheduler:
- 一个任务调度容器中可以注册多个Trigger和JobDetail
- 当Trigger和JobDetail组合时,就可以被任务调度器Scheduler调度了
- 一般情况下,一个应用只需要一个Scheduler对象
-
SchedulerFactory用于创建Scheduler:
- 包括DirectSchedulerFactory和StdSchedulerFactory
- 因为DirectSchedulerFactory使用需要做很多详细的手工编码设计,所以一般StdSchedulerFactory使用较多
-
Scheduler主要有三种:
- RemoteMBeanScheduler
- RemoteScheduler
- StdScheduler
Trigger
- 触发器Trigger: 任务调度的时间规则
- Trigger是Quartz的触发器,会通知任务调度器Scheduler执行指定的任务Job
// 指定触发器首次触发的时间
new Trigger().startAt();
// 指定触发器结束触发的时间
new Trigger().endAt();
复制代码
-
Quartz中提供四种类型的触发器:
-
SimpleTrigger: 可以实现在一个指定时间段内执行一次任务或者在一个时间段内执行多次任务
-
CronTrigger:
- 基于日历的任务调度,功能非常强大
- 相比较于SimpleTrigger精准指定间隔 ,CronTrigger是基于Cron表达式的,更加常用
-
DateIntervalTrigger
-
NthIncludedDayTrigger
-
-
Calendar: 一些日历的特定时间点的集合
- 一个触发器可以包含多个Calendar, 这样可以用包含或者排除某些时间点
JobDetail
-
任务调度程序JobDetail:
- 一个具体的可执行的任务调度程序
- Job就是这个可执行任务调度程序所要执行的内容
- JobDetail中还包含了任务调度的方案和策略
-
JobDetail绑定指定的Job, 每次Scheduler调用执行一个Job时,首先会获取对应的Job, 然后创建该Job的实例,再去执行Job中的execute() 的内容,任务执行结束后,关联的Job对象实例会被释放并且会被JVM的GC清除
-
JobDetail为Job实例提供了许多属性:
- name
- group
- jobClass
- jobDataMap
Job
-
任务Job: 表示要执行的具体工作或者被调度的任务
- 自定义的任务类需要实现该接口,通过重写execute() 方法来定义任务的执行逻辑
-
Job的类型有两种:
-
无状态的stateless job
-
有状态的stateful job
- 对于同一个触发器Trigger来说,有状态的Job不能并行执行,只有上一次触发的任务被执行完成之后,才能触发下一次执行
-
-
Job的属性有两种:
-
volatility: 表示任务是否被持久化到数据库存储
-
durability: 没有trigger关联的时候任务是否保留
- 两者都是在值为true时表示任务被持久化或者保留
- 一个Job可以关联多个Trigger, 一个Trigger只能关联一个Job
-
-
JobExecutionContext:
- JobExecutionContext中包含了Quartz运行时的环境以及Job本身的详细数据信息
- 当Scheduler调度执行一个Job时,就会将JobExecutionContext传递到当前Job的execute() 中,当前Job就可以通过JobExecutionContext对象获取信息
-
Quartz设计成JobDetail + Job的原因在于:
- JobDetail用于定义任务数据, 真正的执行任务的逻辑在Job中
- 因为任务有可能是并发执行的,如果Scheduler直接使用Job, 会存在对同一个Job实例并发访问的问题
- 通过JobDetail绑定Job的方式 ,Scheduler每次执行,都会根据JobDetail创建一个新的Job实例,可以避免并发访问的问题
SpringBoot集成Quartz
-
Quartz使用基本流程:
- 首先创建任务Job, 这是任务的主体,用于编写任务的业务逻辑
- 接着创建任务调度器Scheduler, 这是用来对任务进行调度的,主要用于任务的启动,停止,暂停,恢复等操作
- 接着创建任务明细JobDetail, 这是用来保存任务相关信息,和指定的任务Job相互绑定,用于给调度器执行
- 然后创建触发器Trigger, 这是用来定义任务的触发规则. 主要使用的有SimpleTrigger和CronTrigger两大类触发器
- 最后就是根据触发器Scheduler来启动任务JobDetail和触发器Trigger
创建任务Job
- 创建一个类实现任务Job接口,需要重写execute() 方法,方法内容就是具体的业务执行逻辑
- 如果是动态任务,就需要在创建任务明细JobDetail或者触发器Trigger时动态传入参数,然后通过JobExecutionContext来获取参数进行处理
创建任务调度器Scheduler
- 如果是普通的,则需要通过任务调度器工厂SchedulerFactory创建
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
复制代码
- 在SpringIOC中,可以通过Spring直接注入即可:
@Autowired
private Scheduler scheduler;
复制代码
- 普通模式,通过工厂创建:
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
复制代码
创建任务明细JobDetail
/*
* 通过JobBuilder.newJob()方法获取到当前Job的具体实现,然后通过链式调用添加任务明细
* - 固定的Job实现
* - 如果是动态实现,根据不同的类来创建Job
* - 比如((Job)Class.forName("com.oxford.job.DynamicJob").newInstance()).getClass()
* - 则当前Job的具体实现:JobBuilder.newJob(((Job)Class.forName("com.oxford.job.DynamicJob")).getClass())
*/
JobDetail jobDetail = JobBuilder.newJob(Job.class)
/*
* usingJobData可以以k-v的形式给当前JobDetail添加参数
* 可以通过链式调用usingJobData,传入多个参数
* 在Job实现类中,可以通过JobExecutionContext.getJobDetail().getJobDataMap().get("name")获取参数的值
*/
.usingJobData("name", "oxford")
// 添加认证信息
.withIdentity("name","group")
// 任务明细创建完成后,执行生效
.build()
复制代码
创建触发器Trigger
-
通用的触发器包括两类:
- SimpleTrigger
- CronTrigger
SimpleTrigger
- SimpleTrigger: 根据Quartz框架的类中自定义的方法设置定时执行规则
Trigger trigger = TriggerBuilder.newTrigger()
/*
* usingJobData可以以k-v的形式给当前JobDetail添加参数
* 可以通过链式调用usingJobData,传入多个参数
* 在Job实现类中,可以通过JobExecutionContext.getJobDetail().getJobDataMap().get("name")获取参数的值
*/
.usingJobData("name", "oxford")
// 添加认证信息
.withIdentity("name", "group")
// 添加开始执行时间 - 立即执行startNow()
// 开始执行时间
.startAt(start)
// 结束执行时间.如果不添加这个参数表示永久执行
.endAt(end)
// 添加执行规则.使用SimpleScheduleBuilder添加SimpleTrigger触发器
.withSchedule(SimpleSchedulebuilder
.simpleSchedule()
// 执行间隔,这里是每隔6秒执行一次
.withIntervalInSeconds(6)
.repeatForever())
// 触发器创建完成.执行生效
.build();
复制代码
CronTrigger
- CronTrigger: 基于Cron表达式实现触发器
Trigger trigger = TriggerBuilder.newTrigger()
/*
* usingJobData可以以k-v的形式给当前JobDetail添加参数
* 可以通过链式调用usingJobData,传入多个参数
* 在Job实现类中,可以通过JobExecutionContext.getJobDetail().getJobDataMap().get("name")获取参数的值
*/
.usingJobData("name", "oxford")
// 添加认证信息
.withIdentity("name", "group")
// 添加开始执行时间 - 立即执行startNow()
// 开始执行时间
.startAt(start)
// 结束执行时间.如果不添加这个参数表示永久执行
.endAt(end)
// 添加执行规则.使用CronScheduleBuilder添加CronTrigger触发器
withSchedule(CronScheduleBuilder
.cronSchedule("0 0/2 * * * ?"))
// 触发器创建完成.执行生效
.build();
复制代码
启动任务
// 根据任务明细和任务触发器添加任务
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
复制代码
暂停任务
// 根据触发器中的withIdentity认证信息对任务进行暂停
scheduler.pauseTrigger(Trigger.triggerKey("name", "group"));
复制代码
恢复任务
// 根据触发器中的withIdentity认证信息对任务进行恢复
scheduler.resumeTrigger(Trigger.triggerKey("name", "group"));
复制代码
删除任务
// 先对任务进行暂停
scheduler.pauseTrigger(Trigger.triggerKey("name", "group"));
// 然后移除任务
scheduler.unscheduleJob(Trigger.triggerKey("name", "group"));
// 根据任务明细中的withIdentity认证信息对任务进行删除
scheduler.deleteJob(JobKey.jobKey("name", "group"));
作者:攻城狮Chova
链接:https://juejin.cn/post/6942479525469126686
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。