定时任务
Spring Boot 内置 @Scheduled 注解,开箱即用;复杂场景(持久化、集群去重、动态调整)可集成 Quartz 或 XXL-Job。
启用定时任务
在启动类或任意配置类加 @EnableScheduling:
@SpringBootApplication
@EnableScheduling
public class DemoApplication { ... }@Scheduled 三种触发方式
fixedRate — 固定频率
@Scheduled(fixedRate = 5000) // 每 5 秒执行一次(上次开始后计时)
public void fixedRateTask() {
log.info("执行时间: {}", LocalDateTime.now());
}
// 首次延迟 10 秒后再按频率执行
@Scheduled(initialDelay = 10000, fixedRate = 5000)
public void delayedTask() { ... }fixedDelay — 固定延迟
// 上次执行结束后,间隔 3 秒再执行(适合任务耗时不稳定的场景)
@Scheduled(fixedDelay = 3000)
public void fixedDelayTask() { ... }cron — 表达式
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨 2 点
public void dailyTask() { ... }
@Scheduled(cron = "0 */30 9-18 * * MON-FRI") // 工作日 9-18 点每 30 分钟
public void workHourTask() { ... }
// 使用配置文件中的 cron 表达式(便于动态调整)
@Scheduled(cron = "${app.task.cron:0 0 3 * * ?}")
public void configurableTask() { ... }Cron 表达式格式
Spring 使用 6 位 cron(秒 分 时 日 月 周):
┌─── 秒 (0-59)
│ ┌─── 分 (0-59)
│ │ ┌─── 时 (0-23)
│ │ │ ┌─── 日 (1-31)
│ │ │ │ ┌─── 月 (1-12 或 JAN-DEC)
│ │ │ │ │ ┌─── 周 (0-7 或 MON-SUN,0/7 均为周日)
│ │ │ │ │ │
* * * * * *
| 表达式 | 含义 |
|---|---|
0 * * * * * | 每分钟第 0 秒 |
0 0 * * * * | 每小时整点 |
0 0 0 * * * | 每天 0 点 |
0 0 2 * * ? | 每天凌晨 2 点(? 表示不指定,用于日/周互斥) |
0 0 10,14,16 * * ? | 每天 10、14、16 点 |
0 0/30 9-17 * * MON-FRI | 工作日 9-17 点每 30 分钟 |
0 0 12 1 * ? | 每月 1 日中午 |
0 0 0 L * ? | 每月最后一天 |
线程池配置
@Scheduled 默认单线程执行,多任务会相互阻塞。配置专用线程池:
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
registrar.setScheduler(taskScheduler());
}
@Bean(destroyMethod = "shutdown")
public Executor taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("scheduler-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(30);
return scheduler;
}
}或直接在 application.yml 配置(Spring Boot 2.1+):
spring:
task:
scheduling:
pool:
size: 5
thread-name-prefix: scheduler-异步执行
结合 @Async 让每次调度在独立线程中运行,避免任务间阻塞,详见 异步与线程池:
@Scheduled(fixedRate = 1000)
@Async("taskExecutor") // 指定异步线程池
public void asyncTask() {
// 每次触发都在新线程运行,上一次未完成也不影响
}动态注册任务
SchedulingConfigurer 支持运行时动态添加/取消任务:
@Configuration
public class DynamicSchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
// 从数据库读取任务配置后动态注册
taskConfigs.forEach(config ->
registrar.addCronTask(
() -> executeTask(config.getId()),
config.getCronExpr()
)
);
}
}也可持有 ScheduledTaskRegistrar 引用,在运行时调用 scheduleCronTask() / cancel() 动态增删。
集群环境下的重复执行问题
单机 @Scheduled 在多实例部署时会每个实例都执行,常见解决方案:
方案一:分布式锁(轻量,推荐)
@Scheduled(cron = "0 0 3 * * ?")
public void cleanupTask() {
boolean locked = redisLock.tryLock("task:cleanup", 10, TimeUnit.MINUTES);
if (!locked) return; // 其他实例已在执行
try {
doCleanup();
} finally {
redisLock.unlock("task:cleanup");
}
}详见 分布式锁。
方案二:ShedLock
专为定时任务设计的分布式锁框架,基于数据库或 Redis:
@Scheduled(cron = "0 0 3 * * ?")
@SchedulerLock(name = "cleanupTask",
lockAtLeastFor = "PT5M", // 锁持有最少 5 分钟
lockAtMostFor = "PT1H") // 锁持有最多 1 小时
public void cleanupTask() {
doCleanup();
}<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-spring</artifactId>
</dependency>方案三:Quartz 集群模式
Quartz 通过共享数据库表协调集群,适合任务量大、需要持久化和失败重试的场景:
spring:
quartz:
job-store-type: jdbc # 使用数据库存储
jdbc:
initialize-schema: always
properties:
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.clusterCheckinInterval: 20000优雅停机
确保应用关闭时等待正在执行的任务完成:
spring:
task:
scheduling:
shutdown:
await-termination: true
await-termination-period: 30s详见 优雅停机。
监控与告警
通过 Actuator 可查看定时任务状态:
management:
endpoints:
web:
exposure:
include: scheduledtasks # 暴露 /actuator/scheduledtasks 端点访问 /actuator/scheduledtasks 返回所有已注册的定时任务及其 cron/fixedRate 配置。
相关链接
- 异步与线程池 —
@Async与线程池配置 - 分布式锁 — 防止集群重复执行
- Actuator 监控 — 任务状态可观测
- 优雅停机 — 任务的安全停止
- 配置管理 — cron 表达式外部化配置