首页 > 编程开发 > Java    日期:2026-06-18 / 浏览

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

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

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

核心分类

1. 静态定时任务

  • 基于 @Scheduled 实现
  • 规则写死在代码或配置文件,启动后不可修改
  • 优点:简单、零依赖、开发极快
  • 缺点:不灵活、单线程易阻塞

2. 动态定时任务

  • 基于 SchedulingConfigurer + Trigger 实现
  • 运行时可修改 cron、启停任务,无需重启服务
  • 优点:高度灵活,支持运营后台配置
  • 缺点:代码稍复杂,需要自己维护任务状态

3. 分布式定时任务

  • 如 Quartz、XXL-Job、Elastic-Job
  • 解决集群下任务重复执行问题
  • 本文重点讲 Spring 内置方案,分布式仅作对比

典型业务场景

  • 每日凌晨:2:00 清理 7 天前日志、生成日账单
  • 超时任务:下单 30 分钟未支付自动取消
  • 周期巡检:定时检查服务器状态、接口健康度
  • 动态配置:运营在后台修改任务执行时间

 

二、@Scheduled 静态定时任务

1. 开启定时任务总开关

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

  • 如 Quartz、XXL-Job、Elastic-Job
  • 解决集群下任务重复执行问题
  • 本文重点讲 Spring 内置方案,分布式仅作对比
  • 固定周期:每 5 分钟刷新一次统计数据
  • 每日凌晨:2:00 清理 7 天前日志、生成日账单
  • 超时任务:下单 30 分钟未支付自动取消
  • 周期巡检:定时检查服务器状态、接口健康度
  • 动态配置:运营在后台修改任务执行时间
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/5 表示从 0 开始,每 5 单位
  • -:范围,如 10-20 表示 10 到 20
  • ,:枚举,如 1,3,5 表示 1、3、5

示例:

"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. 核心原理

  • 实现 SchedulingConfigurer
  • 重写 configureTasks 注册任务
  • 使用 CronTrigger 动态读取最新 cron
  • 把 cron 存在 DB/Redis/Nacos 中,实现真正可配置

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. 禁止在定时任务中写超大耗时操作

  • 超过 1 分钟的任务建议丢 MQ 或线程池异步处理。

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

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

解决方案:

  • 数据库乐观锁
  • Redis 分布式锁(Redisson)
  • 使用 XXL-Job 分布式定时任务

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

  • 任务执行失败、超时,通过邮件/钉钉/企业微信告警。

4. cron 尽量避开高峰整点

  • 大量任务都在 0 点执行会导致瞬间压力过大,错开执行。

5. 日志必须清晰

  • 记录任务开始/结束时间、耗时、异常信息,方便排查。

九、总结

  • 1. @EnableScheduling 是定时任务开关,缺一不可;
  • 2. @Scheduled 支持 fixedRate/fixedDelay/cron,生产优先用配置化 cron;
  • 3. 多任务必须配置线程池,避免单线程阻塞;
  • 4. 耗时任务用 @Async 异步化,提升系统稳定性;
  • 5. 动态定时基于 SchedulingConfigurer,可实现运行时修改、启停;
  • 6. 生产务必做异常捕获、监控告警、分布式锁防重复

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章