Quartz
1.核心概念
- Scheduler(任务调度器)
- Trigger(触发器)
- JobDetail(任务详情)
- Job(具体任务)
1.Scheduler
任务调度器, 通过触发器(Trigger)
和任务详情(JobDetail)
来调度、暂停、删除任务, 调度器可以看作一个独立运行的容器, 通过将Trigger
和JobDetail
注册到Scheduler
中, 两者在Scheduler中拥有各自的组和名称
组和名称是Scheduler
查询某一对象的依据, Trigger
的组和名称必须唯一, JobDetail
的组和名称也必须唯一(但可以跟Trigger相同, 因为两者是不同类型)
Scheduler是一个接口
2.Trigger
触发器, 描述Job执行的时间触发规则, 有两个子类: SimpleTrigger
和CronTrigger
,
SimpleTrigger适合仅调度一次、以固定时间间隔周期执行的调度
CronTrigger可以通过Cron表达式定义出各种复杂时间规则的调度方案, 适合更灵活、更复杂的任务调度需求
3.JobDetail
任务详情, 包括了任务的唯一标识以及具体要执行的任务, 可以通过JobDataMap往任务中传递数据
4.Job
具体任务, 是一个接口, 只定义一个方法execute()方法包含了执行任务的具体方法
1.1 核心概念图
- Job: 要做什么事情
- JobDetail: 封装这个任务, 设置组和名称
- Trigger: 什么时候去做
- Scheduler:
什么时候
去做什么事情
一个Scheduler可以注册多个JobDetail和Trigger
2 简单实现
- 任务
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("执行任务");
}
}
- 初始化任务详情和触发器, 并装载到调度器中, 让调度器去执行
public static void main(String[] args) {
// 任务详情
JobDetail JobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.build();
// 触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
// 执行计划, 以1秒的间隔执行, 周期性执行
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.startNow()
.build();
try {
// 调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(JobDetail, trigger); // 装载任务和触发器
scheduler.start(); // 开启定时任务
} catch (SchedulerException e) {
e.printStackTrace();
}
}
注意点: Scheduler每次执行, 都会根据jobDetail创建一个新的job对象, 这样可以规避并发访问的问题(job是多例的, jobDetail也是多例的)
Quartz定时任务默认都是并发执行的, 不会等待上一个任务执行完毕, 只要时间间隔到了, 就会执行. 如果定时任务执行时间太久, 会长时间占用资源, 导致其他任务阻塞
2.1 在运行时, 通过JobDataMap向Job传递数据
- 创建JobDetail(类似于一个Map), 设置需要传递的值, 最后在初始化JobDetail或者Trigger时作为参数传入
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("name", "zhangsan");
jobDataMap.put("age", 15);
jobDataMap.put("money", 128.2);
// 任务详情
JobDetail JobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.usingJobData(jobDataMap)
.build();
- 在Job中获取JobDataMap并访问数据
可以使用getMergedJobDataMap将jobData和triggerData合并在一起, 但会出现相同key被覆盖的情况
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap jobData = jobExecutionContext.getJobDetail().getJobDataMap();
String name = jobData.getString("name");
int age = jobData.getInt("age");
double money = jobData.getDouble("money");
System.out.println(name + age + money);
// 从trigger中获取传递的参数
// JobDataMap triggerData = jobExecutionContext.getTrigger().getJobDataMap();
// 将JobDetail和Trigger
// JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
}
2.2 每次调度任务时, 都会创建新的Job、JobDetail、Trigger
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("job=" + System.identityHashCode(context));
System.out.println("jobDetail=" + System.identityHashCode(context.getJobDetail()));
System.out.println("trigger=" + System.identityHashCode(context.getTrigger()));
System.out.println("scheduler=" + System.identityHashCode(context.getScheduler()));
}
}
scheduler在每次调度任务时, 都会创建新的Job、JobDetail、Trigger, 每个任务调度使用新的JobDetail 和Trigger 对象可以保证任务之间的独立性和隔离性。这样可以避免任务之间的相互影响,提高系统的稳定性和可靠性。
2.3 Quartz定时任务默认都是并发执行的,不会等待上一次任务执行完毕
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("execute:" + new Date());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
设置的时间间隔是1秒, 但是job中需要执行3秒, 但是每次执行定时任务, 并没有等上一次执行结束才开始, 这说明了每次执行定时任务都是并发的, 定时任务之间相互隔离, 也印证了每次调度任务时, 都会创建新的Job、JobDetail、Trigger
2.4 串行执行定时任务
@DisallowConcurrentExecution
禁止并发执行多个相同定义的JobDetail, 这个注解是加在Job类上的, 但意思并不是不能同时执行多个Job, 而是不能并发执行同一个Job Definition(由JobDetail定义), 但是可以同时执行多个不同的JobDetail
@DisallowConcurrentExecution
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("execute:" + new Date());
System.out.println("job=" + System.identityHashCode(context));
System.out.println("jobDetail=" + System.identityHashCode(context.getJobDetail()));
System.out.println("trigger=" + System.identityHashCode(context.getTrigger()));
System.out.println("scheduler=" + System.identityHashCode(context.getScheduler()));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
使用@DisallowConcurrentExecution, 依旧是创建新的Job、JobDetail、Trigger, 但是执行的时间间隔会受到job执行时间的影响
@DisallowConcurrentExecution的作用: 确保定时任务串行执行, 这意味着如果一个定时任务是间隔执行, 下一个任务的执行会等待上一个任务执行完毕才开始
2.4 持久化JobDetail中的JobDataMap
我们在JobDataMap中设置count为1, 然后在job对这个值进行累加
因为Job、JobDetail都是每次执行定时任务时新建的, 所以JobDataMap也不是同一个对象. 所以我们如果想让JobDataMap在一个定时任务中被共享(job、jobDetail), 需要添加@PersistJobDataAfterExecution注解
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
int count = jobDataMap.getInt("count");
System.out.println("count:" + count);
jobDataMap.put("count", jobDataMap.getInt("count") + 1);
}
}
注意: 持久化只针对于JobDetail中的JobDataMap(对于Tigger中的JobDataMap无效)
2.5 有状态的Job和无状态的Job
在Quartz中, 默认的Job是无状态的