Java 定时任务 Quartz —— 并发

Posted 四维胖次

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 定时任务 Quartz —— 并发相关的知识,希望对你有一定的参考价值。

1 前言

根据 Quartz 的设计,一个 Job 可以绑定多个 Trigger,必然会遇到并发的问题。

2 并发

2.1 复现

让我们编写一个并发的例子:

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 public class AcceptConcurrentDemo {
 6 
 7     public static void main(String[] args) throws SchedulerException, InterruptedException {
 8         JobDetail detail = JobBuilder.newJob(AcceptConcurrentJob.class)
 9                 .withIdentity("detail", "group0")
10                 .build();
11 
12 
13         Trigger trigger = TriggerBuilder.newTrigger()
14                 .withIdentity("ben_trigger")
15                 .usingJobData("name", "ben")
16                 .startNow()
17                 .build();
18 
19         Trigger triggers = TriggerBuilder.newTrigger()
20                 .withIdentity("mike_trigger")
21                 .usingJobData("name", "mike")
22                 .forJob("detail", "group0")
23                 .startNow()
24                 .build();
25 
26 
27         Scheduler scheduler = new StdSchedulerFactory().getScheduler();
28 
29         scheduler.start();
30         scheduler.scheduleJob(detail, trigger);
31         scheduler.scheduleJob(triggers);
32         /*
33          * 6 秒钟后关闭
34          */
35         Thread.sleep(6_000);
36         scheduler.shutdown();
37     }
38 
39     @Data
40     public static class AcceptConcurrentJob implements Job {
41         private String name;
42 
43         @Override
44         public void execute(JobExecutionContext context) {
45             try {
46                 System.out.printf("i am %s \\n", name);
47                 Thread.sleep(2_000);
48             } catch (InterruptedException e) {
49                 e.printStackTrace();
50             }
51         }
52     }
53 }

 

请注意上边的 Details 的 Identity ,设置为 group0.detail,同时我们创建了两个 Trigger,第二个 trigger 在创建的时候通过指定 Identity 绑定到了目标 Job,接着提交这个 Job,与两个 Trigger ,可以看到两个触发器同时出发了 Job 的 execute 方法

 

上边的代码也可以简化为以下形式:

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 public class AcceptConcurrentDemo {
 6 
 7     public static void main(String[] args) throws SchedulerException, InterruptedException {
 8         JobDetail detail = JobBuilder.newJob(AcceptConcurrentJob.class)
 9                 .withIdentity("detail", "group0")
10                 .build();
11 
12 
13         Trigger trigger = TriggerBuilder.newTrigger()
14                 .withIdentity("ben_trigger")
15                 .usingJobData("name", "ben")
16                 .startNow()
17                 .build();
18 
19         Trigger triggers = TriggerBuilder.newTrigger()
20                 .withIdentity("mike_trigger")
21                 .usingJobData("name", "mike")
22                 .startNow()
23                 .build();
24 
25 
26         Scheduler scheduler = new StdSchedulerFactory().getScheduler();
27 
28         scheduler.start();
29         scheduler.scheduleJob(detail, Sets.newHashSet(trigger,triggers),true);
30         /*
31          * 6 秒钟后关闭
32          */
33         Thread.sleep(6_000);
34         scheduler.shutdown();
35     }
36 
37     @Data
38     public static class AcceptConcurrentJob implements Job {
39         private String name;
40 
41         @Override
42         public void execute(JobExecutionContext context) {
43             try {
44                 System.out.printf("i am %s \\n", name);
45                 Thread.sleep(2_000);
46             } catch (InterruptedException e) {
47                 e.printStackTrace();
48             }
49         }
50     }
51 }

 

2.2 避免并发

为了避免并发,我们可以使用官方提供的注解 @DisallowConcurrentExecution,通过在 类上增加这个注解,我们可以观察到第二个 trigger 进行了排队处理:

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 public class RejectConcurrentDemo {
 6 
 7     public static void main(String[] args) throws SchedulerException, InterruptedException {
 8         JobDetail detail = JobBuilder.newJob(RejectConcurrentJob.class)
 9                 .withIdentity("detail", "group0")
10                 .build();
11 
12 
13         Trigger trigger = TriggerBuilder.newTrigger()
14                 .withIdentity("ben_trigger")
15                 .usingJobData("name", "ben")
16                 .startNow()
17                 .build();
18 
19         Trigger triggers = TriggerBuilder.newTrigger()
20                 .withIdentity("mike_trigger")
21                 .usingJobData("name", "mike")
22                 .forJob("detail", "group0")
23                 .startNow()
24                 .build();
25 
26 
27         Scheduler scheduler = new StdSchedulerFactory().getScheduler();
28 
29         scheduler.start();
30         scheduler.scheduleJob(detail, trigger);
31         scheduler.scheduleJob(triggers);
32         /*
33          * 6 秒钟后关闭
34          */
35         Thread.sleep(6_000);
36         scheduler.shutdown();
37     }
38 
39 
40     @DisallowConcurrentExecution
41     @Data
42     public static class RejectConcurrentJob implements Job {
43         private String name;
44 
45         @Override
46         public void execute(JobExecutionContext context) {
47             try {
48                 System.out.printf("i am %s \\n", name);
49                 Thread.sleep(2_000);
50             } catch (InterruptedException e) {
51                 e.printStackTrace();
52             }
53         }
54     }
55 }

 

 

3 避免并发的原理探索

让我们找到 JobStore 的实现类,在这里是 RAMJobStore,点进去方法 org.quartz.simpl.RAMJobStore#acquireNextTriggers,可以看到这个方法的某个块:

 

 通过对 Job 类上的是否存在  DisallowConcurrentExecution 注解,如果存在,表示拒绝并发执行 execute 方法。如果与即将执行的 Trigger 调用同一个 JobDetail 对象,则将当前 trigger 放入等待列表。

 

之后当前一个 Trigger 执行完毕,将等待中的 Trigger 重新加回去 RAMJobStore 持有的 trigger 列表中,等待下一次调用发生。

以上是关于Java 定时任务 Quartz —— 并发的主要内容,如果未能解决你的问题,请参考以下文章

Quartz 简单使用

java web定时任务---quartz

Quartz-定时任务框架

Quartz定时任务调度

JavaSpringboot-Quartz-分布式任务调度

Quartz定时任务框架使用教程详解