欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-11 08:02:33
...

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

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

上一章简单介绍了 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的关闭和挂起,并发控制(四)

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

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

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

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

控制台打印输出:

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

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

一.一.二.三 repeatSecondlyForTotalCount(int count) 方法

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

控制台打印输出:

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

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

一.一.二.四 repeatSecondlyForTotalCount(int count, int seconds) 方法

//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever())
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2))
//.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3))
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3, 2))

控制台打印输出:

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

只运行三次,每隔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的关闭和挂起,并发控制(四)

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

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

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

一.一.三.四 注意点

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

 //.endAt(endDate)

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

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

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

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

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

控制台打印输出:

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

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

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

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

控制台打印输出:

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

只执行了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的关闭和挂起,并发控制(四)

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

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

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

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

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

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

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

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

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

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

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

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

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

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 complete 在中间。

二.一.三.三 shutdown (false) 时

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

控制台打印输出:

Quartz的Scheduler的关闭和挂起,并发控制(四)
shutdown complete 在中间。

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

二.一.三.四 shutdown(true) 时

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

控制台打印输出:

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

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的关闭和挂起,并发控制(四)

挂起时间的那五次操作,又给补回来了,重复执行了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() 时添加 @DisallowConcurrentExecution 注解

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

shutdown(false) 时运行程序:

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

shutdown(true) 时运行程序:

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

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

本章节代码链接为:


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


谢谢您的观看!!!