有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot版本2.7.0
在实际开发中,定时任务功能是我们经常需要的,除了JDK 自带,也有很多实现定时任务的框架,比如 Spring、XXL-JOB、Quartz、Elastic Job 等等。
接下来我们学习下在Spring Boot 中,如何使用Spring Scheduler创建定时任务。
使用@EnableScheduling 和@Scheduled注解就能飞速创建定时任务。
在配置类上或启动类上添加@EnableScheduling 注解,开启任务调度。
@Configuration
@EnableScheduling
public class Config {
}
在@Component类中,使用方法创建任务,然后添加@Scheduled,配置cron 表达式,一个简单的任务就编写完成了:
@Component
public class TestTask {
// 每两秒钟执行一次
@Scheduled(cron = "*/2 * * * * ?")
public void test() throws InterruptedException {
// 任务处理逻辑
}
}
@Scheduled注解可配置的属性如下:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
String CRON_DISABLED = "-";
// cron 表达式
String cron() default "";
// 解析 cron 表达式的时区。 默认:""(即使用服务器的本地时区)。
String zone() default "";
// 在上次调用结束和下一次调用开始之间的固定时间段,单位:毫秒,默认:-1(不延迟)
long fixedDelay() default -1L;
// 上次调用的结束和下一次调用的开始之间固定时间间隔字符串,单位:毫秒。
String fixedDelayString() default "";
// 在调用之间的固定时间段
long fixedRate() default -1L;
// 在调用之间的固定时间段字符串
String fixedRateString() default "";
// 在第一次执行 fixedRate 或 fixedDelay 任务之前延迟的毫秒数。
long initialDelay() default -1L;
// 在第一次执行 fixedRate 或 fixedDelay 任务之前延迟的毫秒数字符串。
String initialDelayString() default "";
// 时间单位
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
}
如果使用这种方式,此时所有定时任务都在一个线程中执行。可以在以下代码执行中看出,这两个任务的线程ID 和名称都是同一个。

这样肯定就会存在问题,如果上一个任务没有执行完毕,下一个任务就算到了执行时间,也会一直处于阻塞状态。这样就很容易出现任务执行延迟,甚至丢失任务。
所以Spring 提供了线程池去调度任务,每个任务分配单独的线程去执行,Spring Boot也提供了其自动配置功能。
代码很简单,就是两个属性配置类,和两个自动配置类,从名字上就可以看出,分别为任务执行器和任务调度器,任务调度线程 调度任务执行线程 执定时任务。

自动配置主要是帮我们注册了两个Bean 对象,一个是任务调度线程池。
另外一个是任务执行线程池。

为了解决单线程任务的阻塞问题,接下我们使用Spring Boot为我们提供了自动配置线程池实现多线程异步任务。
首先在配置文件中添加线程池的配置,实际开发中要根据定时任务的数量、间隔时间、服务器性能来合理配置。
spring:
task:
# 任务调度线程池
scheduling:
pool:
# 任务调度线程池大小 默认 1
size: 5
# 调度线程名称前缀,默认scheduling-
thread-name-prefix: scheduling-
shutdown:
# 线程池关闭时等待所有任务完成,默认
await-termination: true
# 线程关闭前最大等待时间
await-termination-period: 100s
# 任务执行线程池
execution:
pool:
# 核心线程数
core-size: 8
# 最大线程数
max-size: 16
thread-name-prefix: mytask-
在配置类或启动类上添加@EnableAsync注解,在任务方法上添加@Async注解。
可以看到,就算任务执行需要3 S,实际只要时间一到就会异步多线程去执行,不会存在阻塞导致任务延迟问题。

然而,在现在微服务盛行的时代,Spring 任务无法提供分布式,只适用于一些简单的单机任务。