Spring Boot 定时任务
定时任务是指系统在固定的时间或者时间间隔,执行指定的任务。
1. 定时任务的实现方式
1)crontab
在 Unix/Linux 系统经常使用 crontab 命令实现定时任务。使用 contab 命令设置固定或者周期性任务后,由系统进程 crond 定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。
其它方式实现的定时任务,通常都会借鉴 crontab 命令的格式。
2)Timer
Timer 是指 Java 自带的 java.util.Timer 类,这个类允许调度一个 java.util.TimerTask 任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。
3)ScheduledExecutorService
ScheduledExecutorService 也是 JDK 自带的一个类。它基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行。也就是说,任务是并发执行,互不影响。
4)Spring Task
Spring Task,可以将它看成一个轻量级的 Quartz,而且使用起来比 Quartz 简单许多。
5)Quartz
Quartz 是一个功能比较强大的的调度器,可以让程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。
2. 基于 Spring Boot 的定时任务
使用 Spring Boot 自带的定时任务,只需要添加相应的注解就可以实现。
1)导入SpringBoot启动包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> </dependency>
2)启动类启用定时
在启动类里加上 @EnableScheduling 即可开启定时。
@SpringBootApplication // 开启定时 @EnableScheduling public class SpringBootDemoTimeTaskApplication { private static final Logger logger = LoggerFactory.getLogger(SpringBootDemoTimeTaskApplication.class); public static void main(String[] args) { SpringApplication.run(SpringBootDemoTimeTaskApplication.class); logger.info("SpringBootDemoTimeTaskApplication start!"); } }
3)创建定时任务实现类
编写定时任务实现类 ScheduleTask,有两个定时任务 task1 和 task2,分别对应计数器 count1 和 count2,每 1 秒输出一次结果。
package com.example.demo; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class ScheduleTask { // 计数器 int count1 = 1; int count2 = 1; // 每隔一秒执行一次 @Scheduled(fixedRate = 1000) public void task1() { System.out.println(Thread.currentThread().getName() + " [task1] 每秒执行一次, 执行第 [" + count1 + "] 次"); count1++; } // 每隔一秒执行一次 @Scheduled(fixedRate = 1000) public void task2() { System.out.println(Thread.currentThread().getName() + " [task2] 每秒执行一次, 执行第 [" + count2 + "] 次"); count2++; } }
运行结果:
scheduling-1 [task2] 每秒执行一次, 执行第 [1] 次 scheduling-1 [task1] 每秒执行一次, 执行第 [1] 次 scheduling-1 [task1] 每秒执行一次, 执行第 [2] 次 scheduling-1 [task2] 每秒执行一次, 执行第 [2] 次 scheduling-1 [task1] 每秒执行一次, 执行第 [3] 次 scheduling-1 [task2] 每秒执行一次, 执行第 [3] 次 scheduling-1 [task1] 每秒执行一次, 执行第 [4] 次 scheduling-1 [task2] 每秒执行一次, 执行第 [4] 次 ...
4)执行时间的配置
在上面的定时任务中,我们在方法上使用 @Scheduled 注解来设置任务的执行时间。常用有三种属性配置方式:
- fixedRate:定义一个按一定频率执行的定时任务。
- fixedDelay:定义一个按一定频率执行的定时任务,与上面不同的是,改属性可以配合initialDelay, 定义该任务延迟执行时间。
- cron:通过 crontab 格式的表达式来配置任务执行时间。
3. 多线程执行定时任务
Spring Boot 执行定时任务默认为单线程,可以看到两个定时任务都已经执行,并且使同一个线程中(scheduling-1)串行执行,如果只有一个定时任务,这样做肯定没问题,当定时任务增多,如果一个任务卡死,会导致其他任务也无法执行。单线程方式中,多个任务会相互影响。
我们可以将定时任务改造为多线程执行方式。
1)多线程配置类 AsyncConfig
增加一个多线程配置类 AsyncConfig。
package com.example.demo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; // 表明该类是一个配置类 @Configuration // 开启异步事件的支持 @EnableAsync public class AsyncConfig { @Bean public Executor c() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setMaxPoolSize(10); taskExecutor.setCorePoolSize(5); taskExecutor.setQueueCapacity(20); return taskExecutor; } }
2)在定时任务的类或者方法上添加 @Async
package com.example.demo; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class ScheduleTask { // 计数器 int count1 = 1; int count2 = 1; // 每隔一秒执行一次 @Scheduled(fixedRate = 1000) // 使用异步执行 @Async public void task1() { System.out.println(Thread.currentThread().getName() + " [task1] 每秒执行一次, 执行第 [" + count1 + "] 次"); count1++; } // 每隔一秒执行一次 @Scheduled(fixedRate = 1000) // 使用异步执行 @Async public void task2() { System.out.println(Thread.currentThread().getName() + " [task2] 每秒执行一次, 执行第 [" + count2 + "] 次"); count2++; } }
此时,可让每一个任务都是在不同的线程中,启动项目,日志打印如下:
SimpleAsyncTaskExecutor-2 [task2] 每秒执行一次, 执行第 [1] 次 SimpleAsyncTaskExecutor-1 [task1] 每秒执行一次, 执行第 [1] 次 SimpleAsyncTaskExecutor-3 [task1] 每秒执行一次, 执行第 [2] 次 SimpleAsyncTaskExecutor-4 [task2] 每秒执行一次, 执行第 [2] 次 SimpleAsyncTaskExecutor-5 [task1] 每秒执行一次, 执行第 [3] 次 SimpleAsyncTaskExecutor-6 [task2] 每秒执行一次, 执行第 [3] 次 SimpleAsyncTaskExecutor-7 [task1] 每秒执行一次, 执行第 [4] 次 SimpleAsyncTaskExecutor-8 [task2] 每秒执行一次, 执行第 [4] 次 ...
日志打印证明使用了多线程执行定时任务。
4. 定时任务设置方式
fixedDelay 和 fixedRate,单位使用毫秒。fixedRate:定义一个按一定频率执行的定时任务。fixedDelay:定义一个按一定频率执行的定时任务。
cron:通过 crontab 格式的表达式来配置任务执行时间。
cron 表达式有专门的语法,cron 共有 7 位,但是最后一位是年,可以留空,所以我们可以写 6 位:
* 第一位,表示秒,取值0-59 * 第二位,表示分,取值0-59 * 第三位,表示小时,取值0-23 * 第四位,日期天/日,取值1-31 * 第五位,日期月份,取值1-12 * 第六位,星期,取值1-7,星期一,星期二...,注:不是第1周,第二周的意思 另外:1表示星期天,2表示星期一。 * 第7为,年份,可以留空,取值1970-2099
cron 中,还有一些特殊的符号,含义如下:
(*)星号:可以理解为每的意思,每秒,每分,每天,每月,每年... (?)问号:问号只能出现在日期和星期这两个位置,表示这个位置的值不确定,每天3点执行,所以第六位星期的位置,我们是不需要关注的,就是不确定的值。同时:日期和星期是两个相互排斥的元素,通过问号来表明不指定值。比如,1月10日,比如是星期1,如果在星期的位置是另指定星期二,就前后冲突矛盾了。 (-)减号:表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12 (,)逗号:表达一个列表值,如在星期字段中使用“1,2,4”,则表示星期一,星期二,星期四 (/)斜杠:如:x/y,x是开始值,y是步长,比如在第一位(秒) 0/15就是,从0秒开始,每15秒,最后就是0,15,30,45,60 另:*/y,等同于0/y
下面列举几个例子:
0 0 3 * * ? 每天3点执行 0 5 3 * * ? 每天3点5分执行 0 5 3 ? * * 每天3点5分执行,与上面作用相同 0 5/10 3 * * ? 每天3点的 5分,15分,25分,35分,45分,55分这几个时间点执行 0 10 3 ? * 1 每周星期天,3点10分 执行,注:1表示星期天 0 10 3 ? * 1#3 每个月的第三个星期,星期天 执行,#号只能出现在星期的位置
Spring Boot 简化了 Spring 应用的创建、运行、调试、部署等一系列问题。Spring Boot 主配置文件默认为 application.yml 或者 application.properties,我们可以根据自己的使用习惯,任取两种格式的文件之一即可。读取配置文件的方式有三种:@Value 注解、@ConfigurationProperties 注解 和 Environment 对象。