Quartz的Scheduler的关闭和挂起,并发控制(四)
勿以恶小而为之,勿以善小而不为--------------------------刘备
劝诸君,多行善事积福报,莫作恶
上一章简单介绍了 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();
}
}
运行程序,控制台打印输出:
一直运行,每1s(默认值) 执行一次
一.一.二.二 repeatSecondlyForever(int seconds) 方法
只需要将调度的那行代码替换可:
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2))
控制台打印输出:
一直运行,每隔两秒(参数传入值)运行一次。
一.一.二.三 repeatSecondlyForTotalCount(int count) 方法
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2))
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3))
控制台打印输出:
只运行了三次,每隔1s(默认值)运行。
一.一.二.四 repeatSecondlyForTotalCount(int count, int seconds) 方法
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2))
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3))
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3, 2))
控制台打印输出:
只运行三次,每隔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之后,才打印出日志信息:
控制台打印输出:
虽然程序还在运行,但并不会执行任务,结束时间到了,一共执行了5次。
延迟4s执行, endDate-startDate=10s, 每2秒执行一次, 故执行 10/2=5 次。
在结束日期到时,会停止执行任务。
一.一.三.四 注意点
在任务类 MyJob9里面,打印了开始日期和结束日期, 如果在主程序里面,并没有设置 endAt(), 即没有设置结束日期, 那么获取时会出错。
//.endAt(endDate)
一.一.三.五 结束日期和运行次数先后关系
一.一.三.二的程序时,调度器是一直运行,但触发器Trigger 设置了结束日期, 当时间到达触发器的结束日期时,不会再继续执行作业调度了。
演示1: 当设置调度的次数时(5,5):
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)) // 设置为简单触发器
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(5,5)) //想执行5次,超过结束日期
控制台打印输出:
只执行了2次, 到 Trigger 结束时间时暂停。
演示2: 当设置调度次数为(2,2)
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)) // 设置为简单触发器
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(5,5)) //想执行5次,超过结束日期
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(2,2)) //想执行2次,未到结束时间
控制台打印输出:
只执行了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();
}
}
运行程序,此时控制台打印输出:
直接关闭,没有任何任务执行。
二.一.二.二 休眠后关闭
改变代码,在关闭之前添加上休眠的代码
//休眠10s后,关闭
Thread.sleep(10000);
scheduler.shutdown();
运行程序,控制台打印输出:
共执行任务6次,(最开始时 1次+休眠时5次=6次),10s之后,关闭了调度器。
二.一.二.三 关闭后重新开启
关闭之后,是否能重新开启呢?
改变代码, 在 二.一.二.二 的基础上再添加一个 ‘重新启动’ 的代码
//休眠10s后,关闭
Thread.sleep(10000);
scheduler.shutdown();
//休眠2s之后,重新启动
Thread.sleep(2000);
scheduler.start();
运行程序,控制台打印输出:
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();
}
}
控制台打印输出:
shutdown complete 在中间。
二.一.三.三 shutdown (false) 时
//休眠10s后,关闭
Thread.sleep(10000);
// 为false 时
scheduler.shutdown(false);
控制台打印输出:
shutdown complete 在中间。
结果,与默认时是一样的。
二.一.三.四 shutdown(true) 时
//休眠10s后,关闭
Thread.sleep(10000);
// 为true 时
scheduler.shutdown(true);
控制台打印输出:
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();
}
}
二.二.三 控制台打印输出
挂起时间的那五次操作,又给补回来了,重复执行了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();
}
}
三.三 控制台打印输出
并不会出现重复执行的问题。
三.四 shutdown() 时添加 @DisallowConcurrentExecution 注解
在 MyJob10.java 上面也添加这个注解
shutdown(false) 时运行程序:
shutdown(true) 时运行程序:
同样,可以达到并发控制的目的。
本章节代码链接为:
链接:https://pan.baidu.com/s/1XKV02pYcFxKU3GqcopziSA
提取码:dbk6
谢谢您的观看!!!
下一篇: 非关系型数据库----NoSql