定时任务总会遇到任务重叠执行的情况,比如一个任务1分钟执行一次,而任务的执行时间超过了1分钟,这样就会有两个相同任务并发执行了。有时候我们是允许这种情况的发生的,比如任务执行的代码是幂等的,而有时候我们可能考虑到一些情况是不允许这种事情发生的。

在实际场景中,我们定时任务调度使用quartz来实现触发的,定时任务的业务代码分布在各个应用,用soa调用。

对于quartz来说,官方文档上明确对这种需求有指定的解决办法,就是使用注解@DisallowConcurrentExecution;

意思是:禁止并发执行多个相同定义的JobDetail,就是我们想要的。

下面一个实现的例子:可以对比两个job:AllowConcurrentExecutionTestJob,DisallowConcurrentExecutionTestJob

  1. public class AllowConcurrentExecutionTestJob implements Job {
  2. public AllowConcurrentExecutionTestJob() {
  3. }
  4.  
  5. public void execute(JobExecutionContext context) throws JobExecutionException {
  6.  
  7. try {
  8. List<JobExecutionContext> list = context.getScheduler().getCurrentlyExecutingJobs();
  9. for(JobExecutionContext jobExecutionContext : list){
  10. // job内部获取容器内变量
  11. System.out.println(jobExecutionContext.getJobDetail().getKey().getName());
  12. }
  13. Thread.sleep(4000);
  14. } catch (SchedulerException e) {
  15. e.printStackTrace();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println("Hello World! AllowConcurrentExecutionTestJob is executing.");
  20. }
  21. }
  1. @DisallowConcurrentExecution
  2. public class DisallowConcurrentExecutionTestJob implements org.quartz.Job {
  3. public DisallowConcurrentExecutionTestJob() {
  4. }
  5.  
  6. public void execute(JobExecutionContext context) throws JobExecutionException {
  7.  
  8. try {
  9. List<JobExecutionContext> list = context.getScheduler().getCurrentlyExecutingJobs();
  10. for(JobExecutionContext jobExecutionContext : list){
  11. // job内部获取容器内变量
  12. System.out.println(jobExecutionContext.getJobDetail().getKey().getName());
  13. }
  14. Thread.sleep(4000);
  15. } catch (SchedulerException e) {
  16. e.printStackTrace();
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println("Hello World! DisallowConcurrentExecutionTestJob is executing.");
  21. }
  22. }

测试代码:

  1. public class QuartzTest {
  2. public static void main(String[] args) throws InterruptedException {
  3.  
  4. try {
  5. // Grab the Scheduler instance from the Factory
  6. Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
  7.  
  8. // and start it off
  9. scheduler.start();
  10.  
  11. // define the job and tie it to our HelloJob class
  12. JobDetail job = JobBuilder.newJob(DisallowConcurrentExecutionTestJob.class)
  13. .withIdentity("job1", "group1")
  14. .build();
  15.  
  16. // Trigger the job to run now, and then repeat every 40 seconds
  17. Trigger trigger = TriggerBuilder.newTrigger()
  18. .withIdentity("trigger1", "group1")
  19. .startNow()
  20. .withSchedule(SimpleScheduleBuilder.simpleSchedule()
  21. .withIntervalInSeconds(1)
  22. .repeatForever())
  23. .build();
  24.  
  25. // define the job and tie it to our HelloJob class
  26. JobDetail job2 = JobBuilder.newJob(AllowConcurrentExecutionTestJob.class)
  27. .withIdentity("job2", "group1")
  28. .build();
  29.  
  30. // Trigger the job to run now, and then repeat every 40 seconds
  31. Trigger trigger2 = TriggerBuilder.newTrigger()
  32. .withIdentity("trigger2", "group1")
  33. .startNow()
  34. .withSchedule(SimpleScheduleBuilder.simpleSchedule()
  35. .withIntervalInSeconds(1)
  36. .repeatForever())
  37. .build();
  38.  
  39. // Tell quartz to schedule the job using our trigger
  40. scheduler.scheduleJob(job2, trigger2);
  41. // scheduler.scheduleJob(job2, trigger2);
  42. // wait trigger
  43. Thread.sleep(20000);
  44. scheduler.shutdown();
  45.  
  46. } catch (SchedulerException se) {
  47. se.printStackTrace();
  48. }
  49. }
  50. }

我们还发现在job的execute里传参是JobExecutionContext,它可以让我们拿到正在执行的job的信息。所以我想在job里直接判断一下就可以知道有没有已经在执行的相同job。

  1. public class SelfDisAllowConExeTestJob implements org.quartz.Job{
  2. public void execute(JobExecutionContext context) throws JobExecutionException {
  3. try {
  4. List<JobExecutionContext> list = context.getScheduler().getCurrentlyExecutingJobs();
  5. Set<String> jobs = new HashSet<String>();
  6. int i=0;
  7. for (JobExecutionContext jobExecutionContext : list){
  8. if(context.getJobDetail().getKey().getName().equals(jobExecutionContext.getJobDetail().getKey().getName())){
  9. i++;
  10. }
  11. }
  12. if(i>1){
  13. System.out.printf("self disallow ");
  14. return;
  15. }
  16. Thread.sleep(4000);
  17. } catch (SchedulerException e) {
  18. e.printStackTrace();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println("Hello World! SelfDisAllowConExeTestJob is executing.");
  23.  
  24. }
  25. }

测试代码:

  1. public class OuartzSelfMapTest {
  2.  
  3. public static void main(String[] args) throws InterruptedException {
  4.  
  5. try {
  6. // Grab the Scheduler instance from the Factory
  7. Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
  8.  
  9. // and start it off
  10. scheduler.start();
  11.  
  12. // define the job and tie it to our HelloJob class
  13. JobDetail job = JobBuilder.newJob(SelfDisAllowConExeTestJob.class)
  14. .withIdentity("job1", "group1")
  15. .build();
  16.  
  17. // Trigger the job to run now, and then repeat every 40 seconds
  18. Trigger trigger = TriggerBuilder.newTrigger()
  19. .withIdentity("trigger1", "group1")
  20. .startNow()
  21. .withSchedule(SimpleScheduleBuilder.simpleSchedule()
  22. .withIntervalInSeconds(1)
  23. .repeatForever())
  24. .build();
  25.  
  26. // Tell quartz to schedule the job using our trigger
  27. scheduler.scheduleJob(job, trigger);
  28.  
  29. // wait trigger
  30. Thread.sleep(20000);
  31. scheduler.shutdown();
  32.  
  33. } catch (SchedulerException se) {
  34. se.printStackTrace();
  35. }
  36. }
  37. }

我们在实际代码中经常会结合spring,特地去看了一下MethodInvokingJobDetailFactoryBean的concurrent属性来控制是否限制并发执行的实现:

  1. Class<?> jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);
  1. /**
  2. * Extension of the MethodInvokingJob, implementing the StatefulJob interface.
  3. * Quartz checks whether or not jobs are stateful and if so,
  4. * won't let jobs interfere with each other.
  5. */
  6. @PersistJobDataAfterExecution
  7. @DisallowConcurrentExecution
  8. public static class StatefulMethodInvokingJob extends MethodInvokingJob {
  9.  
  10. // No implementation, just an addition of the tag interface StatefulJob
  11. // in order to allow stateful method invoking jobs.
  12. }

当然,在quartz里有一个StatefulJob,方便直接继承就实现了concurrent=false的事情了。

那么啰嗦了这么多,其实就是想表达,quartz里并没有一个可以设置说是否并发的接口,而是需要自定义的job自行继承,或使用注解来实现的。

另外,还有一个相关的注解:@PersistJobDataAfterExecution

意思是:放在JobDetail 里的JobDataMap是共享的,也就是相同任务之间执行时可以传输信息。很容易想到既然是共享的,那么就会有并发的问题,就如开头说的这个场景就会导致并发问题。所以官方文档也特别解释这个注解最好和@DisallowConcurrentExecution一起使用。

以下是例子:

  1. @PersistJobDataAfterExecution
  2. public class PersistJob implements Job {
  3. public void execute(JobExecutionContext context) throws JobExecutionException {
  4. JobDataMap data = context.getJobDetail().getJobDataMap();
  5. int i = data.getInt("P");
  6. System.out.printf("PersistJob=>"+i);
  7. i++;
  8. data.put("P", i);
  9. }
  10. }

测试代码:

  1. public class PersistJobDataQuartzTest {
  2. public static void main(String[] args) throws InterruptedException {
  3.  
  4. try {
  5. // Grab the Scheduler instance from the Factory
  6. Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
  7.  
  8. // and start it off
  9. scheduler.start();
  10.  
  11. JobDataMap jobDataMap = new JobDataMap();
  12. jobDataMap.put("P",1);
  13. // define the job and tie it to our HelloJob class
  14. JobDetail job = JobBuilder.newJob(PersistJob.class)
  15. .withIdentity("job1", "group1").usingJobData(jobDataMap)
  16. .build();
  17.  
  18. // Trigger the job to run now, and then repeat every 40 seconds
  19. Trigger trigger = TriggerBuilder.newTrigger()
  20. .withIdentity("trigger1", "group1")
  21. .startNow()
  22. .withSchedule(SimpleScheduleBuilder.simpleSchedule()
  23. .withIntervalInSeconds(1)
  24. .repeatForever())
  25. .build();
  26.  
  27. // Tell quartz to schedule the job using our trigger
  28. scheduler.scheduleJob(job, trigger);
  29. // wait trigger
  30. Thread.sleep(20000);
  31. scheduler.shutdown();
  32.  
  33. } catch (SchedulerException se) {
  34. se.printStackTrace();
  35. }
  36. }
  37. }

参考文档:

 

quartz的一些记录的更多相关文章

  1. Quartz.net使用记录

    1.引入dll文件: nuget控制台:安装quartz:Install-Package Quartz 安装log4net:Install-Package log4net,这里使用log4net记录一 ...

  2. 【Quartz】问题记录注意事项【四】

    记录一:queartz 在同时启动多个任务是,触发器名称不能设置一致,不然第二次启动会不成功 记录二:quartz 在使用任务与触发器分离写法时,任务必须要带(.StoreDurably()) IJo ...

  3. Quartz 基本概念及原理

    最近项目要用quartz,所以记录一下: 概念   Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使 ...

  4. Quartz-2D绘图之图形上下文详解

    上一篇文章大概描述了下Quartz里面大体所包含的东西,但是对具体的细节实现以及如何调用相应API却没有讲.这篇文章就先讲讲图形上下文(Graphics Context)的具体操作. 所谓Graphi ...

  5. iPhone之Quartz 2D系列--图形上下文(2)(Graphics Contexts)

    以下几遍关于Quartz 2D博文都是转载自:http://www.cocoachina.com/bbs/u.php?action=topic&uid=38018 iPhone之Quartz ...

  6. quartz 定时任务的实现

    需求:项目中有一个任务,当时间到了会向移动端通过百度云推送推送信息,之前很傻叉的是写一个多线程一直扫描,每分钟扫描一次,比对当前时间和任务时间是否一样,结果把 项目跑死了,项目中用了一个简单的quar ...

  7. Quartz 2D - 图形上下文(Graphics Contexts)

    一个Graphics Context表示一个绘制目标.它包含绘制系统用于完成绘制指令的绘制参数和设备相关信息.Graphics Context定义了基本的绘制属性,如颜色.裁减区域.线条宽度和样式信息 ...

  8. Quartz.NET学习系列

    Quartz.NET它是一个开源的任务调度引擎,对于周期性任务,持久性任务提供了很好的支持,并且支持持久性.集群等功能. 这是什么对我来说Quartz.NET学习记录: 源代码下载http://yun ...

  9. Quartz 2D编程指南(2) - 图形上下文

    一个Graphics Context表示一个绘制目标.它包含绘制系统用于完成绘制指令的绘制参数和设备相关信息.Graphics Context定义了基本的绘制属性,如颜色.裁减区域.线条宽度和样式信息 ...

随机推荐

  1. 利用dfs解决规定路程问题

    今天继续dfs的训练,遇到了一道神题,不停地TLE,我DD都快碎了.....好在经过本渣不懈努力,还是弄了出来,不容易啊,发上来纪念一下,顺便总结一下关于用dfs解决规定路程的问题. 先上题目: De ...

  2. list容器的C++代码实现

    #include <iostream> using namespace std; template  <class T> class mylist;//前置声明 templat ...

  3. c++(排序二叉树删除)

    相比较节点的添加,平衡二叉树的删除要复杂一些.因为在删除的过程中,你要考虑到不同的情况,针对每一种不同的情况,你要有针对性的反应和调整.所以在代码编写的过程中,我们可以一边写代码,一边写测试用例.编写 ...

  4. for语句,你真正搞懂了吗?

    今天看书时,无意间看到了这个知识点,啥知识点?也许在各位大神看来,那是再简单不过的东西了. 说来惭愧.原来直到今天我才真正搞懂for语句. for语句的结构如下所示: for(语句A;语句B;语句C) ...

  5. linux日志查看命令

    tail tail 命令用于显示文本文件的末尾几行, 对于监控文件日志特别有用 tail example.txt #显示文件 example.txt 的后十行内容: tail -n 20 exampl ...

  6. Mybatis中是否需要依赖配置文件的名称要和mapper接口的名称一致 params错误

    一:当核心配置文件mapper标签下以resource形式指向依赖配置文件时,不需要 这样就可以加载到其相应的依赖配置文件通过namespace找到其相应的方法 二:如果mapper标签下以packa ...

  7. myeclipse中git的使用

    1.右键项目,team-->commit,勾选修改了的文件,点击commit(将更新提交到本地仓库)2.右键项目,team-->pull,合并本地仓库和远程服务器仓库,pull后有一些文件 ...

  8. 常用排序算法java实现

    写在前面:纸上得来终觉浅.基本排序算法的思想,可能很多人都说的头头是到,但能说和能写出来,真的还是有很大区别的. 今天整理了一下各种常用排序算法,当然还不全,后面会继续补充.代码中可能有累赘或错误的地 ...

  9. CUDA与OpenGL互操作

    当处理较大数据量的时候,往往会用GPU进行运算,比如OpenGL或者CUDA.在实际的操作中,往往CUDA实现并行计算会比OpenGL更加方便,而OpenGL在进行后期渲染更具有优势.由于CUDA中的 ...

  10. Thrift之TProtocol系列TBinaryProtocol解析

    首先看一下Thrift的整体架构,如下图: 如图所示,黄色部分是用户实现的业务逻辑,褐色部分是根据thrift定义的服务接口描述文件生成的客户端和服务器端代码框架(前篇2中已分析了thrift ser ...