You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中Quartz编程式调度任务触发时间并发多次执行问题

解决Quartz调度器并发执行多次的问题

咱们来一步步排查你遇到的Quartz任务在触发时间点并发执行多次的问题,结合你给出的代码片段,主要有这几个可能的原因和对应的解决办法:

1. 重复获取Scheduler实例导致旧实例未被关闭

你在初始化调度器时调用了StdSchedulerFactory.getDefaultScheduler(),但在@PreDestroy的 shutdown 方法里又重新获取了一个新的Scheduler实例来关闭——这就会导致最初启动的那个Scheduler根本没被正确关闭!如果应用重启或者上下文刷新,旧的调度器还在后台偷偷运行,新的又启动,自然会出现同一个任务被多个调度器同时触发的情况。

解决办法:把Scheduler实例作为类的成员变量保存下来,复用同一个实例进行初始化和关闭:

private Scheduler scheduler;

// 初始化调度器(用@PostConstruct确保在Bean初始化后执行)
@PostConstruct
public void initScheduler() throws SchedulerException {
    scheduler = StdSchedulerFactory.getDefaultScheduler();
    
    // 创建Job和Trigger
    JobDetail jobDetail = newJob(ExPJob.class).build();
    CronScheduleBuilder cronSchedule = cronSchedule(mycronExpression)
            .withMisfireHandlingInstructionDoNothing();
    Trigger trigger = newTrigger()
            .withSchedule(cronSchedule)
            .build();
    
    scheduler.scheduleJob(jobDetail, trigger);
    scheduler.start();
}

@PreDestroy
public void shutdownSch() throws SchedulerException {
    // 检查实例是否存在且未关闭,再执行关闭
    if (scheduler != null && !scheduler.isShutdown()) {
        scheduler.shutdown(true); // true表示等待正在执行的任务完成后再关闭
    }
}

2. 未禁止Job的并发执行

Quartz默认允许同一个Job的多个实例并发执行,如果你的任务执行时间超过了Cron表达式的间隔,或者因为misfire触发了补跑,就会出现多个任务实例同时运行的情况。你虽然设置了withMisfireHandlingInstructionDoNothing()跳过错过的触发,但如果任务本身执行耗时较长,还是会出现并发问题。

解决办法:在你的ExPJob类上添加@DisallowConcurrentExecution注解,禁止同一个JobDetail的多个实例并发执行:

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

@DisallowConcurrentExecution
public class ExPJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 你的任务业务逻辑
    }
}

3. Spring上下文重复加载导致调度器多次初始化

如果你的应用在启动或部署过程中Spring上下文被多次加载(比如开发环境用了热部署、容器配置问题等),会导致调度器被多次创建并启动,进而引发任务并发执行。

解决办法:用Spring的@Configuration配合@Bean来管理Scheduler的单例,确保调度器只会被初始化一次:

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuartzConfig {

    @Bean
    public Scheduler scheduler() throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        
        JobDetail jobDetail = newJob(ExPJob.class).build();
        CronScheduleBuilder cronSchedule = cronSchedule(mycronExpression)
                .withMisfireHandlingInstructionDoNothing();
        Trigger trigger = newTrigger()
                .withSchedule(cronSchedule)
                .build();
        
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
        return scheduler;
    }
}

Spring会自动管理这个Scheduler Bean的生命周期,在上下文销毁时自动调用shutdown方法(Quartz的StdScheduler实现了DisposableBean接口),无需手动写@PreDestroy。


内容的提问来源于stack exchange,提问作者TriGun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:32:36