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 列表中,等待下一次调用发生。
来源:oschina
链接:https://my.oschina.net/u/4315935/blog/4187648