Skip to content

任务调度 ​


@Scheduled(类比 Laravel Task Scheduling) ​

java
@Component
public class ScheduledTasks {

    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

    @Scheduled(fixedRate = 5000)                  // 每 5 秒执行一次(不管上次是否完成)
    public void reportCurrentTime() {
        log.info("当前时间: {}", LocalDateTime.now());
    }

    @Scheduled(fixedDelay = 5000)                 // 上次执行完 5 秒后再次执行
    public void processPendingJobs() {
        // 适合需要避免重叠执行的任务
    }

    @Scheduled(cron = "0 0 3 * * ?")               // 每天凌晨 3 点执行
    public void dailyCleanup() {
        // 清理过期数据
    }

    @Scheduled(cron = "0 0/5 * * * ?")             // 每 5 分钟执行一次
    public void periodicSync() {
        // 同步数据
    }
}

启用调度 ​

java
@SpringBootApplication
@EnableScheduling                     // 必须加这个注解!否则 @Scheduled 无效
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

和 @EnableCaching 一样,Spring Boot 的功能开关默认是关的,需要手动 @EnableXxx 开启。


三种调度方式 ​

方式用法说明
fixedRate@Scheduled(fixedRate = 5000)固定频率执行,不管上次是否已完成
fixedDelay@Scheduled(fixedDelay = 5000)上次执行完成后间隔指定时间再执行
cron@Scheduled(cron = "0 0 3 * * ?")Cron 表达式

fixedRate 和 fixedDelay 的区别:

  • fixedRate(5000):每隔 5 秒触发一次,如果任务执行了 3 秒,那么下一次在 2 秒后开始
  • fixedDelay(5000):任务完成后等 5 秒再执行下一次,任务耗时不影响间隔

Cron 表达式详解 ​

java
@Scheduled(cron = "秒 分 时 日 月 星期 [年]")
位置含义取值范围
1秒0-59
2分0-59
3时0-23
4日1-31
5月1-12
6星期0-7(0 和 7 都代表周日)
7年(可选)留空或指定年份

常用示例:

Cron 表达式含义
0 0 3 * * ?每天凌晨 3 点
0 0/5 * * * ?每 5 分钟
0 0 9-18 * * ?每天 9 点到 18 点每整点
0 0 8,12,18 * * ?每天 8 点、12 点、18 点
0 0 0 1 * ?每月 1 号零点
0 0 0 ? * MON每周一零点

和 php artisan schedule 的 Cron 表达式格式一致。


⚠️ 常见坑 ​

1. 单线程执行(默认)

Spring 的 @Scheduled 默认使用单一线程池。如果你有多个 @Scheduled 方法,一个卡住,其他都会阻塞。

设置多线程:

java
@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar) {
        registrar.setScheduler(Executors.newScheduledThreadPool(5));  // 5 线程
    }
}

2. fixedRate 不会等待任务完成

java
@Scheduled(fixedRate = 10000)
public void longTask() throws InterruptedException {
    Thread.sleep(20000);  // 任务耗时 20 秒
}
// 虽然 fixedRate=10 秒,但任务执行 20 秒,实际上 20 秒执行一次
// 因为不会重叠执行——下一次触发时如果上次还没完成,会等待上次完成

如果想严格每 10 秒执行一次(允许重叠),需要额外配置。

3. 不想用 Cron 时用 fixedRate/fixedDelay

Cron 表达式难记又容易配错。如果只是固定间隔执行,用 fixedRate 或 fixedDelay 更简单。

4. 生产环境下的分布式锁

多实例部署时,每个实例都会执行 @Scheduled,同一个任务会执行多次。需要引入分布式锁(如 @SchedulerLock + Redis)来确保全局只执行一次。

5. 没有 php artisan schedule:run 的概念

Laravel 需要一个每分钟触发的系统 Cron 来调用 schedule:run。Spring Boot 的 @Scheduled 是进程内的,应用启动后 JVM 自带线程池管理调度,不需要系统 Cron 配合。

但也意味着:应用不运行,调度就不运行。不像 Laravel 那样即使 php-fpm 没请求,系统 Cron 也能触发任务。