一 Job相关
Job 并发
默认的情况下
,无论上一次任务是否结束或者完成,只要规定的时间到了,那么下一次就开始。
有时候会做长时间的任务,比如数据库备份,这个时候就希望上一次备份成功结束之后,才开始下一次备份,即便是规定时间到了,也不能开始,因为这样很有可能造成 数据库被锁死 (几个线程同时备份数据库,引发无法预计的混乱)。
那么在这种情况下,给数据库备份任务增加一个注解
就好了:
@DisallowConcurrentExecution
public class DatabaseBackupJob implements Job {
Job 异常
任务里发生异常是很常见的。 异常处理办法通常是两种:
- 当异常发生,那么就通知所有管理这个 Job 的调度,
停止
运行它 - 当异常发生,
修改一下参数
,马上重新
运行
取消调度
package com.how2java;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class ExceptionJob1 implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
int i = 0;
try {
//故意发生异常
System.out.println(100/i);
} catch (Exception e) {
System.out.println("发生了异常,取消这个Job 对应的所有调度");
JobExecutionException je =new JobExecutionException(e);
je.setUnscheduleAllTriggers(true);
throw je;
}
}
}
修改job参数,继续执行
每个job不同,如果job被多个执行。最好设置static
修饰
因为每次重新执行都是新的job实例
job类
public class ExceptionJob2 implements Job {
static int i = 0;
public void execute(JobExecutionContext context) throws JobExecutionException {
String name= (String) context.getJobDetail().getJobDataMap().get("name");
try {
//故意发生异常
System.out.println(name+this.toString());
System.out.println("运算结果"+100/i);
if(name.equals("exceptionJob2")){
System.out.println("2也有效啊");
}
} catch (Exception e) {
System.out.println(name+" :发生了异常,修改一下参数,立即重新执行");
if(name.equals("exceptionJob1")){
i = 1;
}
JobExecutionException je =new JobExecutionException(e);
je.setRefireImmediately(true);
throw je;
}
}
}
测试代码
@Test
public void exceptionHandle2() {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Trigger trigger = newTrigger().withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(2)
.withRepeatCount(10))
.build();
Trigger trigger2 = newTrigger().withIdentity("trigger2", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(2)
.withRepeatCount(10))
.build();
//定义一个JobDetail
JobDetail job = newJob(ExceptionJob2.class)
.withIdentity("exceptionJob1", "someJobGroup")
.usingJobData("name","exceptionJob1")
.build();
//调度加入这个job
scheduler.scheduleJob(job, trigger);
//等待20秒,让前面的任务都执行完了之后,再关闭调度器
//
JobDetail job2 = newJob(ExceptionJob2.class)
.withIdentity("exceptionJob2", "someJobGroup")
.usingJobData("name","exceptionJob2")
.build();
//调度加入这个job
scheduler.scheduleJob(job2, trigger2);
//启动
scheduler.start();
Thread.sleep(20000);
scheduler.shutdown(true);
}catch (Exception e){
e.printStackTrace();
}
}
异常如果不跑出
只是少了异常抛出的影响(例如控制台打印),但不会影响处理策略(重新执行或停止执行)
中断job
在业务上,有时候需要中断任务,那么这个Job需要实现InterruptableJob
接口,然后就方便中断了
job
package com.how2java;
import org.quartz.InterruptableJob;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.UnableToInterruptJobException;
//必须实现InterruptableJob 而非 Job才能够被中断
public class StoppableJob implements InterruptableJob {
private boolean stop = false;
public void execute(JobExecutionContext context) throws JobExecutionException {
while(true){
if(stop)
break;
try {
System.out.println("每隔1秒,进行一次检测,看看是否停止");
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("持续工作中。。。");
}
}
public void interrupt() throws UnableToInterruptJobException {
System.out.println("被调度叫停");
stop = true;
}
}
测试
private static void stop() throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Trigger trigger = newTrigger().withIdentity("trigger1", "group1")
.startNow()
.build();
//定义一个JobDetail
JobDetail job = newJob(StoppableJob.class)
.withIdentity("exceptionJob1", "someJobGroup")
.build();
//调度加入这个job
scheduler.scheduleJob(job, trigger);
//启动
scheduler.start();
Thread.sleep(5000);
System.out.println("过5秒,调度停止 job");
//key 就相当于这个Job的主键
scheduler.interrupt(job.getKey());
//等待20秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(20000);
scheduler.shutdown(true);
}
二 SimpleTrigger
指定执行一次
启动设置
@Test
public void testMainJob1() throws SchedulerException, InterruptedException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Date startTime = DateBuilder.nextGivenSecondDate(null, 8);
JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();
SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
// schedule it to run!
Date ft = scheduler.scheduleJob(job, trigger);
System.out.println("当前时间是:" + new Date().toLocaleString());
System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());
scheduler.start();
//等待200秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(200000);
scheduler.shutdown(true);
}
打印
当前时间是:2019-7-16 7:16:15
mailGroup.mailJob 这个任务会在 2019-7-16 7:16:16 准时开始运行,累计运行1次,间隔时间是0毫秒
累计n次,间隔n秒
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Date startTime = DateBuilder.nextGivenSecondDate(null, 8);
JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(startTime)
.withSchedule(simpleSchedule()
.withRepeatCount(3)
.withIntervalInSeconds(1))
.build();
// schedule it to run!
Date ft = scheduler.scheduleJob(job, trigger);
System.out.println("当前时间是:" + new Date().toLocaleString());
System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());
scheduler.start();
//等待200秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(200000);
scheduler.shutdown(true);
无限重复
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(startTime)
.withSchedule(simpleSchedule()
.repeatForever()
.withIntervalInSeconds(1))
.build();
三 CronTrigger
Cron表达式生成工具,可以参考一下: http://cron.qqe2.com/
Cron介绍
0/2 * * * * ?
image.png
- 星号():可用在所有字段中,表示对应时间域的
每一个时刻
,例如, 在分钟字段时,表示“每分钟”; - 问号(?):该字符只在日期和星期字段中使用,它通常指定为
“无意义的值”
,相当于占位符
; - 减号(-):表达
一个范围
,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12; - 逗号(,):表达一个
列表
值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;
Cron 表达式举例
image.png四 misfire
misfire:被错过的执行任务策略
相关注解
@PersistJobDataAfterExecution //持久化JobDataMap里的数据,使下一个定时任务还能获取到这些值
@DisallowConcurrentExecution //禁止并发多任务执行,所以永远只有一个任务在执行中
public class StatefulDumbJob implements Job {
在Quartz中,当一个持久的触发器因为调度器被关闭或者线程池中没有可用的线程而错过了激活时间时,就会发生激活失败(misfire)
。那么,我们需要明确2个问题:如何判定
激活失败;如何处理
激活失败。
例如:没有配置Quartz的misfireThreshold,此时使用Quartz的默认misfireThreshold配置为60秒(misfireThreshold是可以进行配置的),设置一个job在上午8点执行,由于一些原因job在8点没有执行,分为两种情况:
- 第一种情况是在8点00分50秒Quartz有资源来执行这个job,此时的延迟执行时间是50秒,小于misfireThreshold为60秒的阀值,则Quartz认为该job没有发生misfire,
立即执行job
。(说明如果在misfireThreshold内又有机会执行时会马上执行
) - 第二种情况是在8点10分00秒Quartz有资源来执行这个job,此时延迟执行时间是600秒,大于misfireThreshold为60秒的阀值,则Quartz认为该job发生了misfire,会根据指定的misfire策略来执行。
如何产生
- 当job达到触发时间时,所有线程都被其他job占用,
没有可用线程
。 - 在job需要触发的时间点,
scheduler停止
了(可能是意外停止的)。 - job使用了
@DisallowConcurrentExecution注解
,job不能并发
执行,当达到下一个job执行点的时候,上一个任务还没有完成。 - job
指定了过去的开始执行时间
,例如当前时间是8点00分00秒,指定开始时间为7点00分00秒。
如何判定
quartz.properties配置文件中有一个属性是 misfireThreshold(单位为毫秒)
,用来指定调度引擎设置触发器超时的"临界值"
。也就是说Quartz对于任务的超时是有容忍度
的,超过
了这个容忍度才会判定
misfire。比如说,某触发器设置为,10:15首次激活,然后每隔3秒激活一次,无限次重复。然而该任务每次运行需要10秒钟的时间。可见,每次任务的执行都会超时,那么究竟是否会引起misfire,就取决于misfireThreshold的值了。以第二次任务来说,它的运行时间已经比预定晚了7
秒,那么如果misfireThreshold>7000,说明该偏差可容忍
,则不算misfire,该任务立刻执行;如果misfireThreshold<=7000,则判定为misfire,根据相关配置策略进行处理。
注意,任务的延迟是有累计
的。在前面的例子中,假设misfireThreshold设置为 60000
,即60秒。那么每次任务的延迟量即是否misfire计算如下:
任务编号 | 预定运行时刻 | 实际运行时刻 | 延迟量(秒) | 备注 |
---|---|---|---|---|
1 | 10:15 | 10:15 | 0 | |
2 | 10:18 | 10:25 | 7 | |
3 | 10:21 | 10:35 | 14 | |
4 | 10:24 | 10:45 | 21 | |
5 | 10:27 | 10:55 | 28 | |
6 | 10:30 | 11:05 | 35 | |
7 | 10:33 | 11:15 | 42 | |
8 | 10:36 | 11:25 | 49 | |
9 | 10:39 | 11:35 | 56 | |
10 | 10:42 | 11:45 | 63 | ` misfire |
在11:45
分发生misfire。那么在11:45第10次任务
会不会准时执行呢?答案是不一定
,取决于配置
。
misfire策略
强烈推荐看:https://blog.csdn.net/chen888999/article/details/78575492。
内容较多就不提炼到本文中了。
这些策略都有例子说明,但在实际应用
中,还是需要对要采用的策略
进行验证
(策略名称
需要多理解)
网友评论