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

Spring中如何切换定时任务?实现单类双Cron任务互斥运行

实现同一时刻仅运行单个定时任务的方案

针对你提出的需求——在同一个@EnableScheduling标注的类中,两个定时任务同一时刻仅运行一个,新任务触发时停止正在运行的旧任务——可以通过跟踪任务执行状态+主动中断旧任务的方式实现,下面是具体的代码示例和详细解释:

核心实现思路

  1. 用AtomicReference<ScheduledFuture<?>>跟踪当前正在执行的任务实例,方便后续取消操作;
  2. 用可重入锁ReentrantLock保证任务切换逻辑的线程安全,避免并发修改任务状态;
  3. 统一封装任务执行入口,在新任务启动前先取消旧任务,再执行新任务;
  4. 在业务逻辑中处理中断异常,确保任务能被及时停止。

完整代码示例

@Service
@EnableScheduling
public class RecognizeService {

    // 原子引用,用于跟踪当前正在执行的任务
    private final AtomicReference<ScheduledFuture<?>> currentRunningTask = new AtomicReference<>();
    // 线程安全锁,确保任务切换逻辑的原子性
    private final ReentrantLock taskLock = new ReentrantLock();

    // 注入Spring的任务调度器,用于提交和管理任务
    @Autowired
    private TaskScheduler taskScheduler;

    @Scheduled(cron = "0/4 * * * * ?")
    public void recognize1() throws IOException, ParseException {
        executeBusinessTask(this::executeRecognize1Logic);
    }

    @Scheduled(cron = "0/4 * * * * ?")
    public void recognize2() throws IOException, ParseException {
        executeBusinessTask(this::executeRecognize2Logic);
    }

    /**
     * 统一任务执行入口,处理旧任务取消和新任务启动
     */
    private void executeBusinessTask(Runnable businessLogic) {
        taskLock.lock();
        try {
            // 取消当前正在运行的任务(如果存在且未完成)
            ScheduledFuture<?> existingTask = currentRunningTask.get();
            if (existingTask != null && !existingTask.isDone()) {
                // true表示中断正在运行的任务线程
                boolean isCancelled = existingTask.cancel(true);
                if (isCancelled) {
                    System.out.println("已中断正在运行的旧任务");
                }
            }
            // 提交新任务并更新当前任务引用
            ScheduledFuture<?> newTask = taskScheduler.schedule(businessLogic, new Date());
            currentRunningTask.set(newTask);
        } finally {
            // 确保锁始终被释放,避免死锁
            taskLock.unlock();
        }
    }

    /**
     * 原recognize1的业务逻辑抽离
     */
    private void executeRecognize1Logic() {
        try {
            System.out.println("开始执行recognize1,时间:" + new Date());
            // 模拟业务执行(比如耗时操作)
            Thread.sleep(3000);
            System.out.println("recognize1执行完成,时间:" + new Date());
        } catch (InterruptedException e) {
            // 处理任务中断,比如清理资源、恢复线程中断状态
            System.out.println("recognize1被中断,停止执行");
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 原recognize2的业务逻辑抽离
     */
    private void executeRecognize2Logic() {
        try {
            System.out.println("开始执行recognize2,时间:" + new Date());
            // 模拟业务执行
            Thread.sleep(3000);
            System.out.println("recognize2执行完成,时间:" + new Date());
        } catch (InterruptedException e) {
            System.out.println("recognize2被中断,停止执行");
            Thread.currentThread().interrupt();
        }
    }
}

关键细节说明

  • 任务中断处理:调用cancel(true)会触发任务线程的InterruptedException,所以业务逻辑中必须捕获该异常并处理,否则任务可能无法及时停止。同时要恢复线程的中断状态(Thread.currentThread().interrupt()),避免影响后续操作。
  • 线程安全保障:ReentrantLock确保在多任务同时触发时,任务切换逻辑(取消旧任务+启动新任务)是原子操作,不会出现并发修改currentRunningTask的问题。
  • TaskScheduler注入:Spring Boot会自动配置TaskScheduler实例,无需额外配置即可直接注入使用。

注意事项

如果你的业务逻辑中涉及IO操作或阻塞调用,需要确保这些操作能响应线程中断,否则即使调用了cancel(true),任务可能仍无法及时停止。比如对于某些不响应中断的阻塞操作,可能需要额外的停止逻辑(比如关闭IO流、设置停止标记等)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 07:59:31