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

Spring Boot中如何保证定时任务任意时刻仅运行单个实例?

Spring Boot定时任务防重入与崩溃锁释放最优方案

原生@Scheduled的能力边界

  • 如果你未修改Spring默认的单线程调度器配置,原生@Scheduled(fixedRate = 600000)本身就不会在当前任务未执行完成时触发新的调度实例,但存在两个明显缺陷:
    • 一旦自定义修改调度线程池大小、或服务多实例部署,该机制完全无法防止任务并发执行
    • 无异常兜底能力,若任务执行过程中出现死锁,后续所有调度都会被阻塞

分场景最优实现

场景1:单实例部署(无多实例需求)

直接使用进程内原子标记实现即可,零额外依赖,完全不存在锁残留问题:

import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class YourScheduleTask {
    // 任务运行状态标记
    private final AtomicBoolean taskRunning = new AtomicBoolean(false);

    @Scheduled(fixedRate = 600000) // 每10分钟触发一次
    public void executeTask() {
        // CAS抢占执行权,抢占失败直接退出,不启动新实例
        if (!taskRunning.compareAndSet(false, true)) {
            return;
        }
        try {
            // 写入你的业务逻辑,执行时长10-20分钟不受影响
        } finally {
            // 无论业务执行成功、抛出异常,都会重置状态
            taskRunning.set(false);
        }
    }
}
  • 该方案下如果应用中途崩溃,JVM进程直接销毁,内存中的状态标记会随进程一起清除,应用重启后第一次调度即可正常执行任务,完全不需要等待锁过期。

场景2:多实例部署

选择ShedLock + Redis实现分布式锁,通过短TTL+心跳续期机制解决崩溃后锁残留等待时间过长的问题:

  • 不要将锁TTL设置为任务最长执行时长(20分钟),改为设置为2~3分钟的短TTL
  • 开启ShedLock内置的锁续期能力,任务正常运行时每隔TTL时长的1/2(即1~1.5分钟)自动续期锁的过期时间,保证任务运行过程中锁不会提前释放
  • 若应用中途崩溃,续期线程随进程一起终止,锁最多等待2~3分钟就会自动过期,不需要等待十几分钟的长TTL
  • 核心配置参数参考:
    # ShedLock锁最小持有时间、最大持有时间
    shedlock.lock-at-least-for=0ms
    shedlock.lock-at-most-for=3m
    # 开启锁续期,续期间隔1.5分钟
    shedlock.lock-extender-interval=1m30s
    

不推荐使用DynamoDB作为锁存储,DynamoDB的TTL为后台异步清理机制,实际过期时间误差可达数十分钟,无法满足锁快速释放的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.13 16:15:54