勿以恶小而为之,勿以善小而不为--------------------------刘备

劝诸君,多行善事积福报,莫作恶

上一章简单介绍了 Quartz传递数据和有无状态Job(三),如果没有看过,​​请观看上一章​​

一. SimpleScheduleBuilder

SimpleScheduleBuilder,简单调度建造者,用于生成调度时间, 接下来,老蝴蝶重点讲解一下,它的用法。

关于它的接口方法,可以观看第二章节的内容。

一.一 调度方法演示

一.一.一 编写 Job 任务

//用于测试  SimpleScheduleBuilder
public class MyJob8 implements Job {

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//要做的事,是打印当前的时间
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化时间
String dateString=sdf.format(new Date());
System.out.println("备份数据库的时间是:"+dateString);
}
}

一.一.二 立即启动,调度方法演示

一.一.二.一 repeatSecondlyForever() 方法
// SimpleScheduleBuilder 方法展示
public class SchedulerDemo8 {
public static void main(String[] args) throws Exception{
//获取Scheduler
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();

// 创建 JobDetail
JobDetail jobDetail=JobBuilder.newJob(MyJob8.class)
.withIdentity("job1","group1")
.build();
//创建 Trigger
Trigger trigger= TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1") //设置标识
.startNow()
//每秒钟执行一次
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
.build();
//关联 job和 trigger
scheduler.scheduleJob(jobDetail,trigger);
//启动 scheduler
scheduler.start();
}
}

运行程序,控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_Quartz的并发控制

一直运行,每1s(默认值) 执行一次

一.一.二.二 repeatSecondlyForever(int seconds) 方法

只需要将调度的那行代码替换可:

//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2))

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_shutdown方法true_02

一直运行,每隔两秒(参数传入值)运行一次。

一.一.二.三 repeatSecondlyForTotalCount(int count) 方法
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2))
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3))

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_shutdown的挂起_03

只运行了三次,每隔1s(默认值)运行。

一.一.二.四 repeatSecondlyForTotalCount(int count, int seconds) 方法
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2))
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3))
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3, 2))

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_shutdown方法true_04

只运行三次,每隔2s运行。

一.一.三 设置开始日期和结束日期启动

上面启动Trigger时,用的是 startNow(), 立即启动,也可以用 startAt(date) 和endAt(date) 来动态地设置 启动时间和结束时间, 常常用于条件触发和条件结束。

可以在任务里面,通过 JobExecutionContext 上下文来获取 开始时间和结束时间。

一.一.三.一 任务接口
//获取开始时间和结束时间
public class MyJob9 implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化时间
String dateString=sdf.format(new Date());
//是设置的开始时间和结束时间,不会改变
System.out.println("开始时间:"+sdf.format(jobExecutionContext.getTrigger().getStartTime()));
System.out.println("结束时间:"+sdf.format(jobExecutionContext.getTrigger().getEndTime()));

System.out.println("备份数据库的时间是:"+dateString);
}
}
一.一.三.二 主程序调用
//startAt 和endAt 的用法
public class SchedulerDemo9 {
public static void main(String[] args) throws Exception{
//获取Scheduler
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();

// 创建 JobDetail
JobDetail jobDetail=JobBuilder.newJob(MyJob9.class)
.withIdentity("job1","group1")
.build();
//当前时间
Date nowDate=new Date();
//开始时间为 当前时间往后4s,即推迟4s执行
Date startDate=new Date(nowDate.getTime()+4000);
//结束时间,往后跑10秒
Date endDate=new Date(startDate.getTime()+10000);

//创建 Trigger
Trigger trigger= TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1") //设置标识
.startAt(startDate)
.endAt(endDate)
// 设置为简单触发器,2s触发一次
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2))
.build();
//关联 job和 trigger
scheduler.scheduleJob(jobDetail,trigger);
//启动 scheduler
scheduler.start();
}
}
一.一.三.三 控制台打印输出

程序运行,大约4s之后,才打印出日志信息:

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_shutdown的挂起_05

虽然程序还在运行,但并不会执行任务,结束时间到了,一共执行了5次。

延迟4s执行, endDate-startDate=10s, 每2秒执行一次, 故执行 10/2=5 次。

在结束日期到时,会停止执行任务。

一.一.三.四 注意点

在任务类 MyJob9里面,打印了开始日期和结束日期, 如果在主程序里面,并没有设置 endAt(), 即没有设置结束日期, 那么获取时会出错。

//.endAt(endDate)

Quartz的Scheduler的关闭和挂起,并发控制(四)_简单调度器的方法使用_06

一.一.三.五 结束日期和运行次数先后关系

一.一.三.二的程序时,调度器是一直运行,但触发器Trigger 设置了结束日期, 当时间到达触发器的结束日期时,不会再继续执行作业调度了。

演示1: 当设置调度的次数时(5,5):

//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)) // 设置为简单触发器
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(5,5)) //想执行5次,超过结束日期

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_shutdown的挂起_07

只执行了2次, 到 Trigger 结束时间时暂停。

演示2: 当设置调度次数为(2,2)

//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)) // 设置为简单触发器
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(5,5)) //想执行5次,超过结束日期
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(2,2)) //想执行2次,未到结束时间

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_Quartz的并发控制_08

只执行了2次。

结束日期和调度次数, 哪个条件先触发,就按照哪个条件的结果走。

二. Scheduler 的开启,关闭和挂起

开启是 start(), 与以前一样, 重点是讲解一下 Scheduler 关闭和挂起。

二.一 关闭 shutdown() 及shutdown(flag)

二.一.一 工作任务类

//shutdown 用法
public class MyJob10 implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//要做的事,是打印当前的时间
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化时间
String dateString=sdf.format(new Date());
System.out.println("备份数据库的时间是:"+dateString);
}
}

二.一.二 主程序测试

二.一.二.一 直接关闭
//shutdown 关闭 
public class SchedulerDemo10 {
public static void main(String[] args) throws Exception{
//获取Scheduler
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();

// 创建 JobDetail
JobDetail jobDetail=JobBuilder.newJob(MyJob10.class)
.withIdentity("job1","group1")
.build();
//创建 Trigger
Trigger trigger= TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1") //设置标识
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)) // 设置为简单触发器
.build();
//关联 job和 trigger
scheduler.scheduleJob(jobDetail,trigger);
//启动 scheduler
scheduler.start();
//直接关闭
scheduler.shutdown();
}
}

运行程序,此时控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_shutdown方法true_09

直接关闭,没有任何任务执行。

二.一.二.二 休眠后关闭

改变代码,在关闭之前添加上休眠的代码

//休眠10s后,关闭
Thread.sleep(10000);

scheduler.shutdown();

运行程序,控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_Quartz的并发控制_10

共执行任务6次,(最开始时 1次+休眠时5次=6次),10s之后,关闭了调度器。

二.一.二.三 关闭后重新开启

关闭之后,是否能重新开启呢?

改变代码, 在 二.一.二.二 的基础上再添加一个 ‘重新启动’ 的代码

//休眠10s后,关闭
Thread.sleep(10000);
scheduler.shutdown();
//休眠2s之后,重新启动
Thread.sleep(2000);
scheduler.start();

运行程序,控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_Quartz的并发控制_11

Exception in thread “main” org.quartz.SchedulerException: The Scheduler cannot be restarted after shutdown() has been called.

同时注意一下, 调度器的关闭顺序

13:02:05,625  INFO QuartzScheduler:666 - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
13:02:05,625 INFO QuartzScheduler:585 - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused.
13:02:05,626 INFO QuartzScheduler:740 - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.

二.一.三 shutdown() 的参数

调用shutdown() 关闭方法时,也可以传入参数

void shutdown(boolean waitForJobsToComplete)  throws SchedulerException;

参数 true 和false, 是有一定的区别的, 下面检测一下。

二.一.三.一 任务调度里面添加 休眠
//shutdown 用法
public class MyJob10 implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

//添加休眠,验证一下 shutdown(flag) 的参数
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
//要做的事,是打印当前的时间
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化时间
String dateString=sdf.format(new Date());
System.out.println("备份数据库的时间是:"+dateString);
}
}
二.一.三.二 shutdown() 默认时
//shutdown 关闭 
public class SchedulerDemo10 {
public static void main(String[] args) throws Exception{
//获取Scheduler
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();

// 创建 JobDetail
JobDetail jobDetail=JobBuilder.newJob(MyJob10.class)
.withIdentity("job1","group1")
.build();
//创建 Trigger
Trigger trigger= TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1") //设置标识
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)) // 设置为简单触发器
.build();
//关联 job和 trigger
scheduler.scheduleJob(jobDetail,trigger);
//启动 scheduler
scheduler.start();

//休眠10s后,关闭
Thread.sleep(10000);

//默认时
scheduler.shutdown();

}
}

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_shutdown方法true_12

shutdown complete 在中间。

二.一.三.三 shutdown (false) 时
//休眠10s后,关闭
Thread.sleep(10000);
// 为false 时
scheduler.shutdown(false);

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_简单调度器的方法使用_13


shutdown complete 在中间。

结果,与默认时是一样的。

二.一.三.四 shutdown(true) 时
//休眠10s后,关闭
Thread.sleep(10000);
// 为true 时
scheduler.shutdown(true);

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)_Quartz的并发控制_14

shutdown complete 在最后。

当 shutdown() 方法 传入 true时, 表示会等所有任务队列里面的任务运行完成后,才关闭。 传入false时,是直接关闭。

注意,无论是直接关闭 false,还是等运行完成后关闭 true, 都不会影响任务队列里面的任务继续运行。

二.二 挂起 standby()

挂起并不是关闭,只是暂时停止运行, 等一段时间后,是可以再次 start() 开启的。

二.二.一 任务接口

//挂起
public class MyJob11 implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//要做的事,是打印当前的时间
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化时间
String dateString=sdf.format(new Date());
System.out.println("备份数据库的时间是:"+dateString);
}
}

二.二.二 主程序测试

//挂起 standby
public class SchedulerDemo11 {
public static void main(String[] args) throws Exception{
//获取Scheduler
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();

// 创建 JobDetail
JobDetail jobDetail=JobBuilder.newJob(MyJob11.class)
.withIdentity("job1","group1")
.build();
//创建 Trigger
Trigger trigger= TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1") //设置标识
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)) // 设置为简单触发器
.build();
//关联 job和 trigger
scheduler.scheduleJob(jobDetail,trigger);
//启动 scheduler
scheduler.start();

//休眠10s后,挂起
Thread.sleep(10000);
scheduler.standby();
//挂起10s后,重新开启
Thread.sleep(10000);
scheduler.start();
}
}

二.二.三 控制台打印输出

Quartz的Scheduler的关闭和挂起,并发控制(四)_shutdown方法true_15

挂起时间的那五次操作,又给补回来了,重复执行了5次。

如果这个任务执行的是数据库插入的操作,那么这个操作就会执行5遍,产生5条除id外相同的记录。

这样是不行的。

有没有一种方式,可以像数据库那样有个锁, 当队列里面有任务未执行结束时,不能进入?

有的,@DisallowConcurrentExecution 注解。

三. 作业任务的并发控制

并发控制,用的是 @DisallowConcurrentExecution 注解, 需要将这个注解放置到工作任务上, 就会形成类似锁的情形。

如果一个任务需要执行很长时间,并且触发器的触发时间较短,导致造成任务等待的问题,一定要处理并发控制。

注解路径: org.quartz.DisallowConcurrentExecution 。

三.一 任务接口

添加 @DisallowConcurrentExecution 注解, 并且休眠5s钟。

@DisallowConcurrentExecution
public class MyJob11 implements Job {

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}

//要做的事,是打印当前的时间
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化时间
String dateString=sdf.format(new Date());
System.out.println("备份数据库的时间是:"+dateString);
}
}

三.二 主程序测试

触发时间为2s, 远远小于 休眠的5s, 会造成任务等待。

//挂起 standby
public class SchedulerDemo11 {
public static void main(String[] args) throws Exception{
//获取Scheduler
Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler();

// 创建 JobDetail
JobDetail jobDetail=JobBuilder.newJob(MyJob11.class)
.withIdentity("job1","group1")
.build();
//创建 Trigger
Trigger trigger= TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1") //设置标识
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)) // 设置为简单触发器
.build();
//关联 job和 trigger
scheduler.scheduleJob(jobDetail,trigger);
//启动 scheduler
scheduler.start();

//休眠10s后,挂起
Thread.sleep(10000);
scheduler.standby();
//挂起10s后,重新开启
Thread.sleep(10000);
scheduler.start();
}
}

三.三 控制台打印输出

Quartz的Scheduler的关闭和挂起,并发控制(四)_调度的shutdown方法_16

并不会出现重复执行的问题。

三.四 shutdown() 时添加 @DisallowConcurrentExecution 注解

在 MyJob10.java 上面也添加这个注解

shutdown(false) 时运行程序:

Quartz的Scheduler的关闭和挂起,并发控制(四)_简单调度器的方法使用_17

shutdown(true) 时运行程序:

Quartz的Scheduler的关闭和挂起,并发控制(四)_Quartz的并发控制_18

同样,可以达到并发控制的目的。

本章节代码链接为:

链接:https://pan.baidu.com/s/1XKV02pYcFxKU3GqcopziSA 
提取码:dbk6



谢谢您的观看!!!