Spring @Scheduled 定时任务详解:Cron表达式、执行顺序、优先级与并行调度

一、@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 任务,这意味着:

  1. 任务按触发时间顺序进入执行队列
  2. 同一时间只能执行一个任务
  3. 如果前一个任务未执行完,后续任务需要等待

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 集群环境下的定时任务

在集群部署时,需要防止同一任务在多个节点重复执行:

  1. 使用分布式锁(Redis、ZooKeeper)
  2. 使用数据库乐观锁
  3. 配置任务只在特定节点执行
  4. 使用专门的调度中心(如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 表达式、配置线程池和任务优先级,可以构建出灵活可靠的定时任务系统。关键要点:

  1. Cron 表达式:掌握特殊字符和常用模式
  2. 执行顺序:默认单线程顺序执行,可通过线程池实现并行
  3. 优先级控制:通过不同线程池或手动同步机制实现
  4. 并行调度 :配置线程池大小,结合 @Async 实现真正异步
  5. 生产实践:监控任务执行时间,避免阻塞,集群环境需防重

根据实际业务需求选择合适的调度策略,平衡性能与可靠性,才能构建出高效的定时任务系统。

相关推荐
yuhaiqiang1 小时前
从这两件事就能看出 vibecoding 距离专业作品差距有多大?AI 能抹平技术,但抹不平品味 !
前端·后端·程序员
一次旅行1 小时前
DeepSeek‑V4‑Flash‑Vision‑Exp 小白入门实战|3种传图方式、完整可跑代码、避坑排障
java·前端·人工智能
野生技术架构师1 小时前
Spring Boot 实现数据脱敏:自定义注解 + Jackson 序列化器
java·spring boot·后端
AI人工智能+电脑小能手1 小时前
大白话说Java设计模式-28-模板方法模式(业务实战篇)
java·设计模式·模板方法模式·spring源码·订单系统·代码复用
奥莱维1 小时前
KNX酒店方案_KNX专用线与高端酒店技术逻辑
java·服务器·前端·数据库
工业一体机老司机2 小时前
Linux工业一体机自启动服务配置:systemd服务单元编写与开机优化实战
java·linux·服务器
weixin_538601972 小时前
智能体测开Day56
java
杨丰玮4182 小时前
从零手写Java飞机躲障碍游戏|Swing绘图、鼠标跟随、计时器碰撞检测实战(五)
java·python·游戏·游戏引擎·图形渲染·动画·贴图