一、@Scheduled 注解简介
Spring Framework 提供了强大的定时任务支持,其中 @Scheduled 注解是实现定时任务最常用的方式。通过在方法上添加此注解,可以轻松配置任务在特定时间点或周期性地执行。
基本使用示例:
java
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTask {
@Scheduled(fixedRate = 5000) // 每5秒执行一次
public void executeTask() {
System.out.println("定时任务执行:" + new Date());
}
}
要启用定时任务功能,需要在配置类上添加 @EnableScheduling 注解:
java
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableScheduling
public class SchedulingConfig {
// 配置类
}
二、Cron 表达式详解
Cron 表达式是定义定时任务执行时间的强大工具,由6个或7个字段组成(Spring 支持6位,Quartz 支持7位)。
2.1 Cron 表达式格式
Spring 中 Cron 表达式的标准格式为:
秒 分 时 日 月 星期 [年]
各字段的取值范围和特殊字符:
| 字段 | 允许值 | 允许的特殊字符 |
|---|---|---|
| 秒(Seconds) | 0-59 | , - * / |
| 分(Minutes) | 0-59 | , - * / |
| 时(Hours) | 0-23 | , - * / |
| 日(Day of month) | 1-31 | , - * ? / L W |
| 月(Month) | 1-12 或 JAN-DEC | , - * / |
| 星期(Day of week) | 0-7 或 SUN-SAT(0和7都表示周日) | , - * ? / L # |
2.2 特殊字符说明
- *:匹配任意值(每秒钟、每分钟等)
- ?:不指定值,仅用于日和星期字段(避免冲突)
- -:范围(如 10-12 表示10、11、12)
- ,:列举多个值(如 MON,WED,FRI 表示周一、三、五)
- /:增量(如 0/15 表示从0开始,每15个单位)
- L:最后(最后一天、最后一个星期几)
- W:工作日(最近的工作日)
- #:第几个星期几(如 6#3 表示第三个星期五)
2.3 常用 Cron 表达式示例
| 表达式 | 说明 |
|---|---|
0 0 12 * * ? |
每天中午12点执行 |
0 15 10 ? * * |
每天上午10:15执行 |
0 0/5 14,18 * * ? |
每天下午2点到2:55期间和下午6点到6:55期间,每5分钟执行一次 |
0 0-5 14 * * ? |
每天下午2点到2:05期间,每分钟执行一次 |
0 0/30 9-17 * * MON-FRI |
工作日上午9点到下午5点,每30分钟执行一次 |
0 0 12 1 * ? |
每月1号中午12点执行 |
0 15 10 15 * ? |
每月15号上午10:15执行 |
0 15 10 L * ? |
每月最后一天上午10:15执行 |
0 15 10 ? * 6L |
每月最后一个星期五上午10:15执行 |
0 15 10 ? * 6#3 |
每月第三个星期五上午10:15执行 |
2.4 @Scheduled 中使用 Cron
java
@Component
public class CronScheduledTasks {
// 每天凌晨1点执行
@Scheduled(cron = "0 0 1 * * ?")
public void dailyTask() {
System.out.println("每日任务执行:" + new Date());
}
// 每30秒执行一次
@Scheduled(cron = "0/30 * * * * ?")
public void every30Seconds() {
System.out.println("每30秒执行:" + new Date());
}
// 工作日每小时执行
@Scheduled(cron = "0 0 * * * MON-FRI")
public void workHourTask() {
System.out.println("工作日每小时执行:" + new Date());
}
}
三、多定时任务的执行顺序与优先级
3.1 默认执行顺序
Spring 默认使用单线程执行所有 @Scheduled 任务,这意味着:
- 任务按触发时间顺序进入执行队列
- 同一时间只能执行一个任务
- 如果前一个任务未执行完,后续任务需要等待
3.2 任务执行时间重叠问题
java
@Component
public class OverlapTasks {
@Scheduled(fixedRate = 2000) // 每2秒执行一次
public void taskA() throws InterruptedException {
System.out.println("Task A 开始:" + new Date());
Thread.sleep(3000); // 模拟耗时3秒
System.out.println("Task A 结束:" + new Date());
}
@Scheduled(fixedRate = 2000) // 每2秒执行一次
public void taskB() {
System.out.println("Task B 执行:" + new Date());
}
}
输出结果可能如下:
Task A 开始:10:00:00
Task A 结束:10:00:03
Task B 执行:10:00:03 // 延迟了1秒
Task A 开始:10:00:04 // 延迟了2秒
...
3.3 配置任务优先级
Spring 本身不直接支持任务优先级,但可以通过以下方式间接控制:
方案一:使用不同的线程池
java
@Configuration
@EnableScheduling
public class PrioritySchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 高优先级任务线程池(核心线程数多)
ThreadPoolTaskScheduler highPriorityScheduler = new ThreadPoolTaskScheduler();
highPriorityScheduler.setPoolSize(5);
highPriorityScheduler.setThreadNamePrefix("high-priority-");
highPriorityScheduler.initialize();
// 低优先级任务线程池(核心线程数少)
ThreadPoolTaskScheduler lowPriorityScheduler = new ThreadPoolTaskScheduler();
lowPriorityScheduler.setPoolSize(2);
lowPriorityScheduler.setThreadNamePrefix("low-priority-");
lowPriorityScheduler.initialize();
// 注册任务到不同的调度器
taskRegistrar.setScheduler(highPriorityScheduler); // 默认使用高优先级
// 可以通过自定义注解或配置来指定任务使用的调度器
}
}
方案二:使用 @Order 注解(有限效果)
java
@Component
@Order(1) // 数字越小优先级越高
public class HighPriorityTask {
@Scheduled(fixedRate = 1000)
public void execute() {
System.out.println("高优先级任务");
}
}
@Component
@Order(2)
public class LowPriorityTask {
@Scheduled(fixedRate = 1000)
public void execute() {
System.out.println("低优先级任务");
}
}
注意 :@Order 主要影响 Bean 的加载顺序,对任务执行顺序的影响有限。
方案三:手动控制执行顺序
java
@Component
public class ManualPriorityTasks {
private final Object lock = new Object();
private boolean highPriorityRunning = false;
@Scheduled(fixedRate = 1000)
public void highPriorityTask() {
synchronized (lock) {
highPriorityRunning = true;
try {
System.out.println("高优先级任务执行");
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
highPriorityRunning = false;
}
}
}
@Scheduled(fixedRate = 1000)
public void lowPriorityTask() {
synchronized (lock) {
if (!highPriorityRunning) {
System.out.println("低优先级任务执行");
}
}
}
}
四、并行调度配置
4.1 启用并行执行
默认情况下,Spring 使用单线程执行定时任务。要启用并行执行,需要配置线程池:
java
@Configuration
@EnableScheduling
public class ConcurrentSchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10); // 设置线程池大小
taskScheduler.setThreadNamePrefix("scheduled-task-");
taskScheduler.setAwaitTerminationSeconds(60);
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskScheduler.initialize();
taskRegistrar.setTaskScheduler(taskScheduler);
}
}
4.2 使用异步执行
结合 @Async 注解实现真正的异步并行:
java
@Configuration
@EnableScheduling
@EnableAsync
public class AsyncSchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-");
return scheduler;
}
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
@Component
public class AsyncScheduledTasks {
@Async
@Scheduled(fixedRate = 1000)
public void asyncTask1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " - Task1 开始");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " - Task1 结束");
}
@Async
@Scheduled(fixedRate = 1000)
public void asyncTask2() {
System.out.println(Thread.currentThread().getName() + " - Task2 执行");
}
}
4.3 不同任务使用不同线程池
java
@Configuration
@EnableScheduling
public class MultiThreadPoolConfig {
@Bean("ioTaskScheduler")
public TaskScheduler ioTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("io-scheduler-");
scheduler.initialize();
return scheduler;
}
@Bean("cpuTaskScheduler")
public TaskScheduler cpuTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(3);
scheduler.setThreadNamePrefix("cpu-scheduler-");
scheduler.initialize();
return scheduler;
}
}
@Component
public class MultiPoolTasks {
private final TaskScheduler ioScheduler;
private final TaskScheduler cpuScheduler;
public MultiPoolTasks(
@Qualifier("ioTaskScheduler") TaskScheduler ioScheduler,
@Qualifier("cpuTaskScheduler") TaskScheduler cpuScheduler) {
this.ioScheduler = ioScheduler;
this.cpuScheduler = cpuScheduler;
// 手动调度IO密集型任务
ioScheduler.scheduleAtFixedRate(() -> {
System.out.println("IO任务执行:" + Thread.currentThread().getName());
}, Duration.ofSeconds(2));
// 手动调度CPU密集型任务
cpuScheduler.scheduleAtFixedRate(() -> {
System.out.println("CPU任务执行:" + Thread.currentThread().getName());
}, Duration.ofSeconds(3));
}
}
五、最佳实践与注意事项
5.1 任务执行时间监控
java
@Component
public class MonitoredScheduledTask {
@Scheduled(fixedRate = 5000)
public void monitoredTask() {
long startTime = System.currentTimeMillis();
try {
// 业务逻辑
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
long duration = System.currentTimeMillis() - startTime;
if (duration > 3000) {
System.err.println("任务执行超时:" + duration + "ms");
}
}
}
}
5.2 避免任务阻塞
- 设置合理的超时时间
- 使用异步执行长时间任务
- 避免在定时任务中进行同步IO操作
- 考虑使用消息队列解耦
5.3 集群环境下的定时任务
在集群部署时,需要防止同一任务在多个节点重复执行:
- 使用分布式锁(Redis、ZooKeeper)
- 使用数据库乐观锁
- 配置任务只在特定节点执行
- 使用专门的调度中心(如XXL-JOB、Elastic-Job)
5.4 动态修改 Cron 表达式
java
@Component
public class DynamicScheduledTask {
private final ScheduledTaskRegistrar registrar;
private ScheduledFuture<?> future;
public DynamicScheduledTask(ScheduledTaskRegistrar registrar) {
this.registrar = registrar;
}
public void scheduleTask(String cronExpression) {
if (future != null) {
future.cancel(false);
}
TaskScheduler scheduler = registrar.getScheduler();
future = scheduler.schedule(() -> {
System.out.println("动态任务执行:" + new Date());
}, new CronTrigger(cronExpression));
}
}
六、总结
@Scheduled 注解是 Spring 定时任务的核心,通过合理使用 Cron 表达式、配置线程池和任务优先级,可以构建出灵活可靠的定时任务系统。关键要点:
- Cron 表达式:掌握特殊字符和常用模式
- 执行顺序:默认单线程顺序执行,可通过线程池实现并行
- 优先级控制:通过不同线程池或手动同步机制实现
- 并行调度 :配置线程池大小,结合
@Async实现真正异步 - 生产实践:监控任务执行时间,避免阻塞,集群环境需防重
根据实际业务需求选择合适的调度策略,平衡性能与可靠性,才能构建出高效的定时任务系统。