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 表达式

fixedRatefixedDelay 的区别:

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

Cron 表达式详解

java
@Scheduled(cron = "秒 分 时 日 月 星期 [年]")
位置含义取值范围
10-59
20-59
30-23
41-31
51-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 表达式难记又容易配错。如果只是固定间隔执行,用 fixedRatefixedDelay 更简单。

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

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

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

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

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

面向 PHP 开发者的 Spring Boot 文档