Spring Boot中使用@Scheduled做定时任务

在 Spring Boot 中,@Scheduled 注解是实现定时任务最常用且便捷的方式,它基于 Spring 的 Task Scheduling 框架,支持多种触发规则,如固定间隔、固定延迟和 Cron 表达式。

基本使用步骤

  1. 启用定时任务 ‌:在 Spring Boot 的主启动类或任意 @Configuration 配置类上添加 @EnableScheduling 注解,以开启对 @Scheduled 注解的支持。

    复制代码
    @SpringBootApplication
    @EnableScheduling
    public class MyApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    }
  2. 创建定时任务 ‌:在任意 Spring 管理的 Bean(如 @Component@Service 等)中,将需要定时执行的方法上添加 @Scheduled 注解,并指定触发规则。

@Scheduled 的触发方式

@Scheduled 注解提供了多种方式来定义任务执行的时间规则,‌必须且只能选择其中一种‌。

  • ‌**cron 表达式(最灵活)**‌:使用 Cron 表达式可以精确控制任务在特定的时间点执行,例如每天、每周的特定时刻。

    复制代码
    @Component
    public class CronTask {
        // 每天凌晨 2 点执行
        @Scheduled(cron = "0 0 2 * * ?")
        public void dailyTask() {
            System.out.println("每日定时任务执行: " + new Date());
        }
    
        // 每 5 秒执行一次
        @Scheduled(cron = "0/5 * * * * ?")
        public void everyFiveSeconds() {
            System.out.println("每5秒执行: " + new Date());
        }
    }
  • ‌**fixedRate(固定速率)** ‌:从‌上一次任务开始执行的时间点 ‌算起,每隔指定的毫秒数执行一次。‌无论上一次任务是否执行完毕,到时间点就会触发下一次执行‌。

    复制代码
    @Component
    public class FixedRateTask {
        // 每 5000 毫秒(5秒)执行一次,从方法开始执行时计时
        @Scheduled(fixedRate = 5000)
        public void fixedRateTask() {
            System.out.println("固定速率任务执行: " + new Date());
            try {
                Thread.sleep(3000); // 模拟任务执行耗时3秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
  • ‌**fixedDelay(固定延迟)** ‌:从‌上一次任务执行结束的时间点‌算起,等待指定的毫秒数后,再执行下一次任务。这种方式能确保任务不会重叠。

    复制代码
    @Component
    public class FixedDelayTask {
        // 上一次任务执行结束后,等待 5000 毫秒(5秒)再执行下一次
        @Scheduled(fixedDelay = 5000)
        public void fixedDelayTask() {
            System.out.println("固定延迟任务执行: " + new Date());
            try {
                Thread.sleep(3000); // 模拟任务执行耗时3秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
  • ‌**initialDelay(初始延迟)** ‌:通常与 fixedRatefixedDelay 配合使用,用于指定任务在应用启动后,‌首次执行前的延迟时间‌。

    @Component
    public class InitialDelayTask {
    // 应用启动后延迟 10 秒,之后每隔 5 秒执行一次
    @Scheduled(initialDelay = 10000, fixedRate = 5000)
    public void initialDelayTask() {
    System.out.println("初始延迟任务执行: " + new Date());
    }
    }

Cron 表达式语法

Cron 表达式由 6 或 7 个字段组成(秒 分 时 日 月 周 [年]),字段间用空格分隔。常用符号如下:

字段 允许值 特殊符号 说明
0-59 * / , - * 表示每秒,/ 表示间隔(如 5/15 表示 5, 20, 35, 50 秒),, 表示列举(如 5,20
0-59 * / , -
0-23 * / , -
1-31 * / , - ? L W ? 表示不指定,L 表示最后一天,W 表示最近的工作日
1-12 或 JAN-DEC * / , -
1-7 或 SUN-SAT * / , - ? L # ? 表示不指定,L 表示最后一天,# 表示第几个(如 6#3 表示每月第三个星期五)
1970-2099 * / , - 可选

常见示例‌:

  • 0 0 12 * * ?:每天中午 12:00:00 执行。
  • 0 15 10 ? * *:每天上午 10:15:00 执行。
  • 0 0/5 * * * ?:每 5 分钟执行一次。
  • 0 0 2 ? * 6L:每月最后一个星期五的凌晨 2:00:00 执行。
  • 0 0 12 15 * ?:每月 15 日中午 12:00:00 执行。

多线程与并发执行

默认情况下,Spring Boot 的 @Scheduled 任务是‌单线程串行执行‌的。这意味着如果一个任务执行时间过长,会阻塞其他任务的执行。

要实现‌多任务并发执行 ‌,需要自定义一个 TaskScheduler Bean,并设置线程池大小:

复制代码
复制代码
@Configuration
public class SchedulerConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10); // 设置线程池大小为10
        scheduler.setThreadNamePrefix("scheduled-task-");
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.initialize();
        return scheduler;
    }
}

此外,如果任务本身是耗时操作(如调用外部 API),可以结合 @Async 注解实现异步执行,但这需要额外配置 @EnableAsync

注意事项

  • 时区问题 ‌:Cron 表达式默认使用服务器的本地时区。如需指定时区,可使用 zone 属性,例如 @Scheduled(cron = "0 0 2 * * ?", zone = "Asia/Shanghai")
  • 分布式环境‌:在多个服务实例部署时,每个实例都会执行定时任务,可能导致任务重复执行。此时应考虑使用分布式调度框架(如 XXL-JOB、Quartz)或结合 Redis 分布式锁来保证任务的唯一性。
  • 任务持久化 ‌:@Scheduled 任务是内存级的,应用重启后任务信息会丢失。如需持久化,需集成 Quartz 等框架。
相关推荐
Heo1 小时前
深入React19任务调度器Scheduler
前端·javascript·面试
一枚前端小姐姐1 小时前
Vue3 + Pinia 状态管理,从入门到模块化
前端·vue.js
用户14436183400972 小时前
你不知道的JS上-(九)
前端·javascript
yuki_uix2 小时前
为什么我的 Auth Token 藏在了 Network 面板的 Doc 里?
前端·python·debug
不会敲代码12 小时前
从原子CSS到TailwindCSS:现代前端样式解决方案全解析
前端·css·react.js
Wect2 小时前
LeetCode 102. 二叉树的层序遍历:图文拆解+代码详解
前端·算法·typescript
简离2 小时前
VSCode Git Bash 终端:告别内置vi,直接用VSCode编辑交互内容
前端
肉肉不想干后端2 小时前
联合订单并发退款:一次分布式锁冲突的排查与思考
java
用户4745189475102 小时前
全链路日志追踪利器:trace-spring-boot-starter 实战指南
java