java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot定时任务@Scheduled基础与动态定时

SpringBoot定时任务@Scheduled基础与动态定时详解

作者:希望永不加班

本文详细解析了SpringBoot定时任务的实现方法,包括静态定时任务、动态定时任务及异步定时任务,并提供了完整的实现方案和注意事项;重点强调了使用@@Scheduled;注解配置定时任务及使用@Async注解异步化执行的重要性

在后端开发中,定时任务属于刚需基础组件:订单超时自动关单、优惠券定时失效、日志/垃圾文件定时清理、统计报表凌晨生成、缓存定时刷新、分布式数据同步……几乎所有中后台项目都会用到。

SpringBoot 自带的定时任务方案开箱即用,无需引入中间件(如 Quartz、XXL-Job),轻量化、无依赖。

一、定时任务整体架构与适用场景

核心分类

1. 静态定时任务

2. 动态定时任务

3. 分布式定时任务

典型业务场景

二、@Scheduled 静态定时任务

1. 开启定时任务总开关

启动类添加 @EnableScheduling,否则定时任务不生效:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling   // 开启 Spring 定时任务
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2. @Scheduled 三大核心参数

@Scheduled 共支持 3 种触发规则,不可同时混用,只能选其一。

(1)fixedRate:固定频率执行

// 每 5 秒执行一次(从上一次启动时算)
@Scheduled(fixedRate = 5000)
public void taskFixedRate() {
    System.out.println("fixedRate 每5秒执行:" + LocalDateTime.now());
}

(2)fixedDelay:固定延迟执行

// 上一次执行完,等 5 秒再执行
@Scheduled(fixedDelay = 5000)
public void taskFixedDelay() {
    System.out.println("fixedDelay 执行完延迟5秒:" + LocalDateTime.now());
}

(3)cron:Cron 表达式

// 每 10 秒执行一次
@Scheduled(cron = "0/10 * * * * ?")
public void taskCron() {
    System.out.println("cron 每10秒执行:" + LocalDateTime.now());
}

3. 初始延迟:initialDelay

项目启动后,不立即执行,等待一段时间再开始:

// 项目启动延迟 3 秒后,每 5 秒执行一次
@Scheduled(initialDelay = 3000, fixedRate = 5000)
public void taskInitialDelay() {
    System.out.println("启动延迟3秒后执行");
}

4. Cron 表达式完整详解

语法结构:

秒(0-59) 分(0-59) 时(0-23) 日(1-31) 月(1-12) 周(1-7,1=周日,7=周六)

常用符号:

示例:

"0 0 2 * * ?"        = 每天凌晨 2 点
"0 0 0 * * ?"        = 每天午夜 0 点
"0 0/5 * * * ?"      = 每 5 分钟
"0 0 12 * * ?"       = 每天中午 12 点
"0 0 8 ? * MON-FRI"  = 工作日早上 8 点
"0 0 0 1 * ?"        = 每月 1 号零点
"0/1 * * * * ?"      = 每秒执行

5. 从配置文件读取

硬编码 cron 不利于维护,推荐放到 application.yml

# 定时任务配置
scheduled:
  task1:
    cron: "0/5 * * * * ?"
  task2:
    fixed-rate: 10000
    initial-delay: 5000

使用:

@Scheduled(cron = "${scheduled.task1.cron}")
public void configTask() {
    System.out.println("从yml读取cron执行");
}

三、解决单线程阻塞问题

Spring 定时任务默认是单线程,多个任务会排队,一个卡死全部卡住。

1. 配置定时任务线程池

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
@EnableScheduling
public class ScheduledThreadPoolConfig {
    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        // 核心线程数,根据任务数量设置
        scheduler.setPoolSize(10);
        // 线程名称前缀,方便日志排查
        scheduler.setThreadNamePrefix("scheduled-task-");
        // 等待任务完成再关闭
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        // 关闭最大等待时间
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.initialize();
        return scheduler;
    }
}

配置后,多个任务可并行执行,互不阻塞。

四、异步 + 定时(@Async + @Scheduled)

让定时任务异步执行,不占用调度线程,进一步提升稳定性。

1. 开启异步

启动类加 @EnableAsync

@SpringBootApplication
@EnableScheduling
@EnableAsync   // 开启异步
public class Application {}

2. 异步定时任务

@Component
public class AsyncScheduleTask {
    @Async
    @Scheduled(cron = "0/5 * * * * ?")
    public void asyncTask() {
        System.out.println("异步定时任务执行:" + Thread.currentThread().getName());
    }
}

优点:

五、动态定时任务

@Scheduled 启动后不可改 cron,运营后台配置、动态调整必须用动态定时。

1. 核心原理

2. 完整动态定时任务实现

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
@Configuration
public class DynamicScheduleConfig implements SchedulingConfigurer {
    // 动态 cron(真实项目从数据库/Redis读取)
    private String cron = "0/5 * * * * ?";
    // 任务启停开关
    private final AtomicBoolean taskEnabled = new AtomicBoolean(true);
    // 外部修改 cron
    public void setCron(String cron) {
        this.cron = cron;
        log.info("动态修改cron成功:{}", cron);
    }
    // 启停任务
    public void setTaskEnabled(boolean enabled) {
        this.taskEnabled.set(enabled);
        log.info("任务状态已修改:{}", enabled ? "开启" : "关闭");
    }
    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar) {
        // 注册动态任务
        registrar.addTriggerTask(
            // 任务业务逻辑
            () -> {
                if (!taskEnabled.get()) {
                    log.info("任务已关闭,跳过执行");
                    return;
                }
                log.info("动态定时任务执行:{}", LocalDateTime.now());
                // 执行业务逻辑...
            },
            // 动态触发器:每次执行前重新获取cron
            triggerContext -> {
                CronTrigger cronTrigger = new CronTrigger(cron);
                return cronTrigger.nextExecutionTime(triggerContext);
            }
        );
    }
}

3. 提供接口动态控制

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/schedule")
public class ScheduleController {
    @Autowired
    private DynamicScheduleConfig dynamicScheduleConfig;
    // 动态修改 cron
    @GetMapping("/updateCron")
    public String updateCron(@RequestParam String cron) {
        try {
            dynamicScheduleConfig.setCron(cron);
            return "修改成功,新cron:" + cron;
        } catch (Exception e) {
            return "cron 格式非法:" + e.getMessage();
        }
    }
    // 开启/关闭任务
    @GetMapping("/enable")
    public String enableTask(@RequestParam boolean enabled) {
        dynamicScheduleConfig.setTaskEnabled(enabled);
        return "任务已" + (enabled ? "开启" : "关闭");
    }
}

测试接口:

# 修改为每10秒执行一次
http://localhost:8080/schedule/updateCron?cron=0/10 * * * * ?
# 关闭任务
http://localhost:8080/schedule/enable?enabled=false

无需重启,立即生效!

六、定时任务异常处理

定时任务一旦抛异常且未捕获,会导致后续调度失效,必须做异常防护。

1. 统一 try-catch 防护

@Scheduled(cron = "${scheduled.task1.cron}")
public void safeTask() {
    try {
        // 业务逻辑
        System.out.println("任务执行");
    } catch (Exception e) {
        // 记录日志 + 告警
        log.error("定时任务执行异常", e);
    }
}

2. 全局异常捕获

结合 AOP 或全局异常切面统一捕获,避免每个任务写 try-catch。

七、静态 vs 动态 方案对比

方案

优点

缺点

适用场景

@Scheduled 静态

极简、零依赖、上手快

不可修改、单线程阻塞

固定周期、不常变更的任务

动态定时(Trigger)

运行可改、可启停、灵活配置

代码稍复杂

运营后台配置、动态调整、多环境适配

异步定时

不阻塞调度、高可用

需注意线程安全、事务

耗时任务、批量处理任务

八、注意事项

1. 禁止在定时任务中写超大耗时操作

2. 集群部署必须防止重复执行

Spring 内置定时不支持分布式,集群下会多节点同时执行。

解决方案:

3. 关键任务必须加监控告警

4. cron 尽量避开高峰整点

5. 日志必须清晰

九、总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文