SpringBoot 定时任务:@Scheduled 基础与动态定时

张开发
2026/4/16 1:31:23 15 分钟阅读

分享文章

SpringBoot 定时任务:@Scheduled 基础与动态定时
在后端开发中定时任务属于刚需基础组件订单超时自动关单、优惠券定时失效、日志/垃圾文件定时清理、统计报表凌晨生成、缓存定时刷新、分布式数据同步……几乎所有中后台项目都会用到。SpringBoot 自带的定时任务方案开箱即用无需引入中间件如 Quartz、XXL-Job轻量化、无依赖。一、定时任务整体架构与适用场景1. 核心分类1.静态定时任务• 基于Scheduled实现• 规则写死在代码或配置文件启动后不可修改• 优点简单、零依赖、开发极快• 缺点不灵活、单线程易阻塞2.动态定时任务• 基于SchedulingConfigurerTrigger实现• 运行时可修改 cron、启停任务无需重启服务• 优点高度灵活支持运营后台配置• 缺点代码稍复杂需要自己维护任务状态3.分布式定时任务• 如 Quartz、XXL-Job、Elastic-Job• 解决集群下任务重复执行问题• 本文重点讲 Spring 内置方案分布式仅作对比2. 典型业务场景• 固定周期每 5 分钟刷新一次统计数据• 每日凌晨2:00 清理 7 天前日志、生成日账单• 超时任务下单 30 分钟未支付自动取消• 周期巡检定时检查服务器状态、接口健康度• 动态配置运营在后台修改任务执行时间二、Scheduled 静态定时任务1. 开启定时任务总开关启动类添加EnableScheduling否则定时任务不生效import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; SpringBootApplication EnableScheduling // 开启 Spring 定时任务 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }2. Scheduled 三大核心参数Scheduled共支持 3 种触发规则不可同时混用只能选其一。1fixedRate固定频率执行• 含义从上一次任务开始时间计算每隔指定毫秒执行一次• 任务执行时间 间隔时间 → 任务会排队不会并行// 每 5 秒执行一次从上一次启动时算 Scheduled(fixedRate 5000) public void taskFixedRate() { System.out.println(fixedRate 每5秒执行 LocalDateTime.now()); }2fixedDelay固定延迟执行• 含义从上一次任务执行结束时间计算等待指定毫秒再执行• 保证任务串行不会重叠// 上一次执行完等 5 秒再执行 Scheduled(fixedDelay 5000) public void taskFixedDelay() { System.out.println(fixedDelay 执行完延迟5秒 LocalDateTime.now()); }3cronCron 表达式• 支持秒级复杂规则每天、每周、每月、指定时分秒• 格式秒 分 时 日 月 周// 每 10 秒执行一次 Scheduled(cron 0/10 * * * * ?) public void taskCron() { System.out.println(cron 每10秒执行 LocalDateTime.now()); }3. 初始延迟initialDelay项目启动后不立即执行等待一段时间再开始// 项目启动延迟 3 秒后每 5 秒执行一次 Scheduled(initialDelay 3000, fixedRate 5000) public void taskInitialDelay() { System.out.println(启动延迟3秒后执行); }4. Cron 表达式完整详解语法结构秒(0-59) 分(0-59) 时(0-23) 日(1-31) 月(1-12) 周(1-71周日7周六)常用符号•*每一秒/每一分/每一时刻•?不指定日和周互斥必须有一个为 ?•/步长如0/5表示从 0 开始每 5 单位•-范围如10-20表示 10 到 20•,枚举如1,3,5表示 1、3、5示例0 0 2 * * ? 每天凌晨 2 点 0 0 0 * * ? 每天午夜 0 点 0 0/5 * * * ? 每 5 分钟 0 0 12 * * ? 每天中午 12 点 0 0 8 ? * MON-FRI 工作日早上 8 点 0 0 0 1 * ? 每月 1 号零点 0/1 * * * * ? 每秒执行5. 从配置文件读取硬编码 cron 不利于维护推荐放到application.yml# 定时任务配置 scheduled: task1: cron: 0/5 * * * * ? task2: fixed-rate: 10000 initial-delay: 5000使用Scheduled(cron ${scheduled.task1.cron}) public void configTask() { System.out.println(从yml读取cron执行); }三、解决单线程阻塞问题Spring 定时任务默认是单线程多个任务会排队一个卡死全部卡住。1. 配置定时任务线程池import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; Configuration EnableScheduling public class ScheduledThreadPoolConfig { Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler new ThreadPoolTaskScheduler(); // 核心线程数根据任务数量设置 scheduler.setPoolSize(10); // 线程名称前缀方便日志排查 scheduler.setThreadNamePrefix(scheduled-task-); // 等待任务完成再关闭 scheduler.setWaitForTasksToCompleteOnShutdown(true); // 关闭最大等待时间 scheduler.setAwaitTerminationSeconds(60); scheduler.initialize(); return scheduler; } }配置后多个任务可并行执行互不阻塞。四、异步 定时Async Scheduled让定时任务异步执行不占用调度线程进一步提升稳定性。1. 开启异步启动类加EnableAsyncSpringBootApplication EnableScheduling EnableAsync // 开启异步 public class Application {}2. 异步定时任务Component public class AsyncScheduleTask { Async Scheduled(cron 0/5 * * * * ?) public void asyncTask() { System.out.println(异步定时任务执行 Thread.currentThread().getName()); } }优点• 任务执行耗时不影响调度• 异常不会导致调度线程崩溃五、动态定时任务Scheduled启动后不可改 cron运营后台配置、动态调整必须用动态定时。1. 核心原理• 实现SchedulingConfigurer• 重写configureTasks注册任务• 使用CronTrigger动态读取最新 cron• 把 cron 存在 DB/Redis/Nacos 中实现真正可配置2. 完整动态定时任务实现import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.support.CronTrigger; import java.time.LocalDateTime; import java.util.concurrent.atomic.AtomicBoolean; Slf4j Configuration public class DynamicScheduleConfig implements SchedulingConfigurer { // 动态 cron真实项目从数据库/Redis读取 private String cron 0/5 * * * * ?; // 任务启停开关 private final AtomicBoolean taskEnabled new AtomicBoolean(true); // 外部修改 cron public void setCron(String cron) { this.cron cron; log.info(动态修改cron成功{}, cron); } // 启停任务 public void setTaskEnabled(boolean enabled) { this.taskEnabled.set(enabled); log.info(任务状态已修改{}, enabled ? 开启 : 关闭); } Override public void configureTasks(ScheduledTaskRegistrar registrar) { // 注册动态任务 registrar.addTriggerTask( // 任务业务逻辑 () - { if (!taskEnabled.get()) { log.info(任务已关闭跳过执行); return; } log.info(动态定时任务执行{}, LocalDateTime.now()); // 执行业务逻辑... }, // 动态触发器每次执行前重新获取cron triggerContext - { CronTrigger cronTrigger new CronTrigger(cron); return cronTrigger.nextExecutionTime(triggerContext); } ); } }3. 提供接口动态控制import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController RequestMapping(/schedule) public class ScheduleController { Autowired private DynamicScheduleConfig dynamicScheduleConfig; // 动态修改 cron GetMapping(/updateCron) public String updateCron(RequestParam String cron) { try { dynamicScheduleConfig.setCron(cron); return 修改成功新cron cron; } catch (Exception e) { return cron 格式非法 e.getMessage(); } } // 开启/关闭任务 GetMapping(/enable) public String enableTask(RequestParam boolean enabled) { dynamicScheduleConfig.setTaskEnabled(enabled); return 任务已 (enabled ? 开启 : 关闭); } }测试接口# 修改为每10秒执行一次 http://localhost:8080/schedule/updateCron?cron0/10 * * * * ? # 关闭任务 http://localhost:8080/schedule/enable?enabledfalse无需重启立即生效六、定时任务异常处理定时任务一旦抛异常且未捕获会导致后续调度失效必须做异常防护。1. 统一 try-catch 防护Scheduled(cron ${scheduled.task1.cron}) public void safeTask() { try { // 业务逻辑 System.out.println(任务执行); } catch (Exception e) { // 记录日志 告警 log.error(定时任务执行异常, e); } }2. 全局异常捕获结合 AOP 或全局异常切面统一捕获避免每个任务写 try-catch。七、静态 vs 动态 方案对比方案优点缺点适用场景Scheduled 静态极简、零依赖、上手快不可修改、单线程阻塞固定周期、不常变更的任务动态定时Trigger运行可改、可启停、灵活配置代码稍复杂运营后台配置、动态调整、多环境适配异步定时不阻塞调度、高可用需注意线程安全、事务耗时任务、批量处理任务八、注意事项1.禁止在定时任务中写超大耗时操作超过 1 分钟的任务建议丢 MQ 或线程池异步处理。2.集群部署必须防止重复执行Spring 内置定时不支持分布式集群下会多节点同时执行。解决方案• 数据库乐观锁• Redis 分布式锁Redisson• 使用 XXL-Job 分布式定时任务3.关键任务必须加监控告警任务执行失败、超时通过邮件/钉钉/企业微信告警。4.cron 尽量避开高峰整点大量任务都在 0 点执行会导致瞬间压力过大错开执行。5.日志必须清晰记录任务开始/结束时间、耗时、异常信息方便排查。九、总结1.EnableScheduling是定时任务开关缺一不可2.Scheduled支持fixedRate/fixedDelay/cron生产优先用配置化 cron3. 多任务必须配置线程池避免单线程阻塞4. 耗时任务用Async异步化提升系统稳定性5. 动态定时基于SchedulingConfigurer可实现运行时修改、启停6. 生产务必做异常捕获、监控告警、分布式锁防重复。

更多文章