Quartz JobDataMap数据存储存在的坑

场景

现在有这么一个场景,我们要统计一个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);
    }
}
相关推荐
QQ_21696290964 分钟前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端
郑州光合科技余经理2 小时前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php
IT_陈寒2 小时前
Vue的嵌套组件竟然吃掉了我的事件?
前端·人工智能·后端
大辉狼_音频架构3 小时前
进阶:从源码编译 SOF 固件与 topology
后端
大辉狼_音频架构3 小时前
上板:让 SOF 在 FRDM-i.MX8MP 上跑起来
后端
大辉狼_音频架构3 小时前
FRDM-IMX8MP UUU 烧录 eMMC 指南
后端
运行时异常4 小时前
【WMS 仓储系统集成 AI Agent 实战】第 3 讲:Spring Security 6 + JWT——addFilterBefore 一词之差,全站 401
java·后端
LinMINGJing0074 小时前
PageHeaderData:Page 的页面头
后端
2601_962069774 小时前
SpringBoot开发——初步了解SpringBoot
java·spring boot·后端
jay神4 小时前
【计算机毕业设计】基于Spring Boot的宠物领养管理系统
java·spring boot·后端·vue·毕业设计·宠物