Spring 任务调度(Task Scheduling)是企业级项目中真正意义上的“进阶必备特性”。
从最简单的 @Scheduled 到生产级分布式定时任务,几乎所有后台系统都离不开它。
以下是 2025 年最新的、最地道的 Spring Boot 3.x(Spring 6 + JDK 17+)任务调度全攻略。
1. 核心对比总览(2025 最新推荐)
| 方式 | 是否支持集群分布式 | 是否支持动态增删改查 | 是否支持持久化 | 是否支持延迟/周期任务 | 推荐场景 |
|---|---|---|---|---|---|
@Scheduled + 单机 | 不支持 | 只能重启 | 不支持 | 支持 | 简单项目、小型服务 |
@Scheduled + ShedLock | 支持(轻量锁) | 只能重启 | 依赖数据库/Redis | 支持 | 中型项目,快速分布式化 |
| Spring Boot 3 自带 Scheduling | 不支持 | 支持(动态任务) | 不支持 | 支持 | 需要动态开关、修改cron的任务 |
| Quartz Scheduler(官方推荐) | 支持 | 支持(完全动态+UI) | 支持(数据库) | 支持所有类型 | 大中型、核心业务、银行级定时任务 |
| XXL-JOB / ElasticJob / PowerJob | 支持 | 支持(带管理后台) | 支持 | 支持 | 超大型分布式系统、需要任务分片、失败告警等 |
2. 基础用法:@Scheduled(90% 项目够用)
@Component
@EnableScheduling // 主类或配置类上必须加
public class SimpleScheduledTasks {
// 固定延迟:上一次执行完成时间点之后 5 秒再执行
@Scheduled(fixedDelay = 5000)
public void reportCurrentTime() {
System.out.println("固定延迟:" + LocalDateTime.now());
}
// 固定频率:上一次开始时间点之后 5 秒执行(即使上一次还没执行完)
@Scheduled(fixedRate = 5000)
public void fixedRateTask() throws InterruptedException {
Thread.sleep(8000);
System.out.println("固定频率:" + LocalDateTime.now());
}
// Cron 表达式(最常用)
@Scheduled(cron = "0 */2 * * * ?") // 每2分钟的第0秒执行
public void cronTask() {
System.out.println("Cron任务:" + LocalDateTime.now());
}
// 启动后延迟 10 秒,然后每 5 秒执行一次
@Scheduled(initialDelay = 10000, fixedRate = 5000)
public void withInitialDelay() { ... }
}
Cron 表达式速查(2025 版)
秒 分 时 日 月 星期 年(可选)
0 0 2 * * ? 每天凌晨2点
0 0/30 9-17 * * ? 工作日9点到17点每30分钟
0 0 12 ? * MON-FRI 每周一到周五中午12点
0 15 10 L * ? 每月最后一天10:15
### 3. 生产必备:分布式锁防止重复执行(推荐 ShedLock)
集群部署后,同一个 `@Scheduled` 会在每台机器上都执行一次,必须加锁!
xml
net.javacrumbs.shedlock shedlock-spring 5.10.2
net.javacrumbs.shedlock shedlock-provider-jdbc-template 5.10.2
java
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = “PT30M”) // 最多锁30分钟
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.usingDbTime() // 使用数据库时间,防止服务器时间不同步
.build()
);
}
}
使用方式:
java
@Scheduled(cron = “0 0 1 * * ?”)
@SchedulerLock(name = “dailyClearTask”, lockAtLeastFor = “PT10M”, lockAtMostFor = “PT12H”)
public void dailyClear() {
// 只有拿到锁的节点才会执行
}
### 4. 动态定时任务(无需重启即可增删改cron)
Spring Boot 3 原生支持动态任务(超级好用!
java
@Service
public class DynamicTaskService {
private final TaskScheduler taskScheduler;
private ScheduledFuture<?> future;
public DynamicTaskService(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
// 启动一个可动态修改的定时任务
public void startCronTask(String cron) {
stopTask(); // 先停掉旧的
this.future = taskScheduler.schedule(
() -> System.out.println("动态任务执行:" + LocalDateTime.now()),
new CronTrigger(cron)
);
}
public void stopTask() {
if (future != null) {
future.cancel(true);
}
}
// 对外提供修改接口
@PostMapping("/task/cron")
public R<Void> updateCron(@RequestBody String newCron) {
startCronTask(newCron);
return R.ok();
}
}
### 5. 终极方案:Quartz(银行、核心系统标配)
yaml
application.yml
spring:
quartz:
job-store-type: jdbc # 持久化到数据库
jdbc:
initialize-schema: always # 自动建表
properties:
org:
quartz:
scheduler:
instanceId: AUTO
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
isClustered: true # 开启集群
threadPool:
threadCount: 25
核心代码:
java
@Service
public class QuartzService {
private final Scheduler scheduler;
// 动态添加任务
public void addJob(String jobName, String cron) throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity(jobName)
.storeDurably()
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobName + "_trigger")
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
// 支持暂停、恢复、删除、立即触发等
}
“`
6. 推荐架构(2025 真实生产实践)
| 项目规模 | 推荐方案 | 说明 |
|---|---|---|
| 小项目/个人项目 | 纯 @Scheduled + @EnableScheduling | 够用,简单 |
| 中型项目(3~10台服务器) | @Scheduled + ShedLock | 5分钟搞定分布式,零侵入性 |
| 大中型项目(10+服务器) | 原生 Quartz(JDBC 集群模式) | 官方、稳定、支持任务持久化、失败重试、MISFIRE策略 |
| 超大规模/互联网公司 | XXL-JOB 或 PowerJob | 自带管理后台、分片广播、失败告警、路由策略、滚动日志等 |
7. 常见坑 & 最佳实践
- 永远不要在
@Scheduled方法里抛出未捕获异常 → 会导致定时任务线程死亡,后续不再执行! - 长时间任务一定要用
fixedDelay,不要用fixedRate。 - 业务代码要幂等!定时任务可能因为 MISFIRE 重试多次。
- 生产环境一定要加监控:任务执行耗时、成功率、最后执行时间。
- 使用
@Async + @Scheduled时注意线程池不要被打满。
需要我直接给你一个完整的生产级模板项目吗?包含:
- ShedLock + MySQL 分布式锁
- 动态任务管理接口
- Quartz 集群版
- 统一任务日志表 + 监控告警
直接说一声,我发你 GitHub 仓库地址。