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 列表中,等待下一次调用发生。

posted on 2020-03-30 23:41  四维胖次  阅读(1966)  评论(0编辑  收藏  举报