任务调度
@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 也能触发任务。