场景
现在有这么一个场景,我们要统计一个job执行了多少次,然后打印出来. 这里我们使用JobDataMap展示一个错误案例。
错误实现
less
@Slf4j
class JobCountTest {
@Test
@SneakyThrows
@SuppressWarnings("all")
void run() {
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = stdSchedulerFactory.getScheduler();
JobDetail job = newJob(CounterJob.class)
.withIdentity("job1", "group1")
.build();
// 一秒后执行,三秒一次
Integer repeatCount = 6;
Integer intervalSeconds = 3;
SimpleTrigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startAt(Date.from(Instant.now().plusSeconds(1)))
.withSchedule(simpleSchedule()
.withIntervalInSeconds(intervalSeconds)
.withRepeatCount(repeatCount))
.build();
// 初始化
job.getJobDataMap().put("counter", 0);
scheduler.scheduleJob(job, trigger);
scheduler.start();
Thread.sleep(repeatCount * intervalSeconds * 1000 + 1);
scheduler.shutdown();
assertEquals(repeatCount, job.getJobDataMap().getInt("counter"));
}
}
less
@Slf4j
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class CounterJob implements Job {
@Override
public void execute(JobExecutionContext context) {
jobDataMap.put("counter", context.getJobDetail().getJobDataMap().getInt("counter") + 1);
log.info("--- job: " + context.getJobDetail().getKey() + " executed.");
}
}
结果
分析
经过简单的debug会发现counter在CounerJob的execute方法执行中确实叠加了,但是当所有job执行结束时发现counter的累积值消失了,并且发现在test中获取的jobDataMap和CounterJob利用context获取的jobDataMap不是同一个对象。 所以经过初步分析怀疑jobDataMap对象可能是拷贝对象。进入源码发现
jobDataMap的维护非常简单,并不是拷贝,那我们只能怀疑它的封装对象jobDetail是拷贝了, 从这里看起
我们的数据是存储在内存的,所以应该是这个RAMJobStore
最后这里印证了我们的猜想,scheduleJob时引用的JobDetail和执行时引用的JobDetail实例并不是同一个而是clone对象,所以他们的jobDataMap自然也就不一样, 使用context获取的jobDataMap和test中获取的jobDataMap更不是同一个对象!
正确代码
由于quartz的job是多线程的,我们只要有办法做到线程共享数据就行,这里我们用最简单的方式实现,静态变量。
less
@Slf4j
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class CounterJob implements Job {
public static Integer counter;
@Override
public void execute(JobExecutionContext context) {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
counter = jobDataMap.getInt("counter") + 1;
jobDataMap.put("counter", counter);
log.info("--- job: " + context.getJobDetail().getKey() + " executed.");
}
}
less
@Slf4j
class JobCountTest {
@Test
@SneakyThrows
@SuppressWarnings("all")
void run() {
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = stdSchedulerFactory.getScheduler();
JobDetail job = newJob(CounterJob.class)
.withIdentity("job1", "group1")
.build();
// 一秒后执行,三秒一次
Integer repeatCount = 6;
Integer intervalSeconds = 3;
SimpleTrigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startAt(Date.from(Instant.now().plusSeconds(1)))
.withSchedule(simpleSchedule()
.withIntervalInSeconds(intervalSeconds)
.withRepeatCount(repeatCount))
.build();
// 初始化
job.getJobDataMap().put("counter", 0);
scheduler.scheduleJob(job, trigger);
scheduler.start();
Thread.sleep(repeatCount * intervalSeconds * 1000 + 1);
scheduler.shutdown();
// assertEquals(repeatCount, job.getJobDataMap().getInt("counter"));
assertEquals(repeatCount, CounterJob.counter);
}
}