Spring Boot 定时任务

流程

开启定时任务功能

在SpringBoot的启动类或配置类上添加@EnableScheduling注解,启用Spring的调度器来执行定时任务

@SpringBootApplication
@EnableScheduling
public class MyApplication {
   public static void main(String[] args) {
       SpringApplication.run(MyApplication.class, args);
   }
}

定义定时任务

使用@Scheduled注解标记方法,定义该方法为一个定时任务

@Scheduled支持多种方式定义任务的执行周期,包括fixedRate、fixedDelay和cron表达式

@Service
public class MyTaskService {
​
   // 每 5 秒执行一次任务
   @Scheduled(fixedRate = 5000)
   public void executeTask() {
       System.out.println("Task executed at " + LocalDateTime.now());
   }
​
   // 在上一个任务执行完成后延迟 3 秒再执行
   @Scheduled(fixedDelay = 3000)
   public void executeTaskWithFixedDelay() {
       System.out.println("Task with fixed delay executed at " + LocalDateTime.now());
   }
​
   // 使用 cron 表达式指定任务执行时间
   @Scheduled(cron = "0 0 * * * ?")
   public void executeTaskWithCron() {
       System.out.println("Task executed using cron at " + LocalDateTime.now());
   }
}
​

@Scheduled工作原理

  • SpringBoot中的定时任务调度是基于Spring的Task Scheduler实现的,默认情况下,它使用单线程调度器(SimpleAsyncTaskScheduler)来执行任务
  • 每次应用启动时,Spring Boot会扫描标注了@Scheduled的方法,并根据配置创建调度任务。每个任务都会按照定义的周期独立执行

常用的@Scheduled属性

  • fixedRate:按照固定速率执行任务,任务之间的间隔时间不考虑任务的执行时间,单位毫秒
  • fixedDelay:在上一个任务执行完成后延迟指定时间再执行下一个任务,单位毫秒
  • cron:通过cron表达式定义执行时间,灵活、执行复杂的时间调度

cron表达式

cron表达式是一种非常灵活的时间表达方式,可以精确定义任务的执行时间

cron有6或7个字段,分别表示秒、分、小时、日期、月份、星期几和年份

cron表达式结构

秒 分 时 日 月 星期 [年]

示例

  • 每小时整点执行任务:0 0 * * * ?
  • 每天中午12点执行任务:0 0 12 * * ?
  • 每月1号中午12点执行任务:0 0 12 1 * ?

常用特殊符号

  • *:表示任意值
  • ?:表示无指定值(常用于日或星期字段)
  • /:表示步进,如0/5表示从0开始,每隔5单位执行一次
  • -:表示范围,入0-5表示从1到5
  • ,:表示列出具体值,如1,3,5表示第1、3、5分钟

并发定时任务的处理

默认情况下,Spring Boot的定时任务是使用单线程的调度器,因此多个任务可能会串行执行。对于并发定时任务,可以使用自定义线程池来提高任务调度的并发

示例:配置并发线程池

@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
​
   @Override
   public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
       ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
       taskScheduler.setPoolSize(10);  // 设置线程池大小
       taskScheduler.initialize();
       taskRegistrar.setTaskScheduler(taskScheduler);
   }
}
​

在这个例子中,线程池的大小设置为10,允许同时执行多个定时任务,避免因单线程调度导致任务排队

动态调整定时任务

有时候,我们可以需要在运行时动态调整定时任务的执行时间或禁用某些任务,可以通过将定时任务的配置抽取到配置文件中,并动态读取这些配置来实现动态调整任务

配置文件

task:
  cron: "0 0/1 * * * ?"

动态读取

@Service
public class DynamicTaskService {
​
   @Value("${task.cron}")
   private String cronExpression;
​
   @Scheduled(cron = "#{@dynamicTaskService.cronExpression}")
   public void dynamicScheduledTask() {
       System.out.println("Dynamic task executed at " + LocalDateTime.now());
   }
}
​

并发与锁机制

如果同一任务被并发执行,可能会引起数据不一致或资源争夺问题,为了解决这个问题,可以使用数据库锁或分布式锁(如Redis)来确保任务的单实例执行

@Service
public class LockableTaskService {
​
   @Autowired
   private RedisTemplate<String, String> redisTemplate;
​
   @Scheduled(fixedRate = 60000)
   public void lockableTask() {
       String lockKey = "task-lock";
       Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 5, TimeUnit.MINUTES);
       if (Boolean.TRUE.equals(success)) {
           try {
               // 执行任务
           } finally {
               redisTemplate.delete(lockKey);
           }
       }
   }
}
​

简单异步执行

可以结合@Async实现定时任务的异步执行

@Component
public class AsyncScheduledTasks {
​
    @Async
    @Scheduled(fixedRate = 5000)
    public void asyncTask() {
        // 异步执行任务
    }
}
​
最后修改:2025 年 04 月 21 日
如果觉得我的文章对你有用,请随意赞赏