实现定时任务 Springboot整合
1.cron表达式
cron表达式来指明定时任务的时间,Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义
结构:corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份
各字段的含义:
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒(Seconds) | 0~59的整数 | , - * / 四个字符 |
分(Minutes) | 0~59的整数 | , - * / 四个字符 |
小时(Hours) | 0~23的整数 | , - * / 四个字符 |
日期(DayofMonth) | 1~31的整数(但是你需要考虑你月的天数) | ,- * ? / L W C 八个字符 |
月份(Month) | 1~12的整数或者 JAN-DEC | , - * / 四个字符 |
星期(DayofWeek) | 1~7的整数或者 SUN-SAT (1=SUN) | , - * ? / L C # 八个字符 |
年(可选,留空)(Year) | 1970~2099 | , - * / 四个字符 |
注意事项:每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
- :表示匹配该域的任意值。假如在Minutes域使用, 即表示每分钟都会触发事件。
- ?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
- -:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
- /:表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
- ,:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
- L:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
- W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。
- LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
- #:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
2.Spring Boot原生做定时任务 解决阻塞问题
使用两个注解实现定时任务:
-
@EnableScheduling:开启定时任务功能
-
@Scheduled:设置执行的cron表达式
-
springboot中cron表达式的区别
- 需要注意:spring中的cron表达式无法解析年份,只能有6位,超过6位会报错
- spring的cron中周的位置 1-7 代表周一到周日
-
springboot中定时任务默认是阻塞的.会等一个定时任务处理完成后再调用(在开发中定时任务不应该阻塞)
-
解决方案:
-
方法一:可以让定时任务中的业务以异步的方式(CompletableFuture),自己提交到线程池
-
@Scheduled(cron = "* * * * * ?") public void helloAsync(){ CompletableFuture.runAsync(()->{ log.info("hello..."); },这里放一个线程池对象); }
-
-
方法二:修改spring默认配置(TaskSchedulingProperties),设置支持定时任务的线程池,原配置中线程池中线程数量只有一个,因此在配置文件中设置多个线程数量.(有些版本springboot无效,原因未知.)
-
spring.task.scheduling.pool.size=5
-
-
方法三:直接让定时任务异步执行(异步定时任务)
-
使用@EnableAsync和@Async注解实现
-
底层实现也是将方法提交给线程池异步执行.可在配置类中修改线程池参数
-
spring.task.execution.pool.core-size=5 spring.task.execution.pool.max-size=50 #等各自线程池7大参数设置
-
实现代码见下文
-
-
简单定时任务案例:
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 1.@EnableScheduling开启定时任务功能
* 2.@Scheduled 设置执行时间
*/
@Slf4j
@Component
@EnableScheduling
public class HelloSchedule {
/**
* springboot中的cron表达式没有year 只有6位
*/
@Scheduled(cron = "* * * * * ?")
public void hello(){
log.info("hello...");
}
}
结果:
该定时任务每秒执行一次
2020-09-23 11:19:16.003 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:19:17.001 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:19:18.002 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:19:19.002 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:19:20.000 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:19:21.001 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:19:22.002 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:19:23.003 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:19:24.000 INFO 6640 --- [ scheduling-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
异步定时任务案例:
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 定时任务
* 1.@EnableScheduling开启定时任务功能
* 2.@Scheduled 设置执行时间
*
* 异步任务
* 1.@EnableAsync 开启异步任务功能
* 2.@Async 标注在方法上,使得方法异步执行
*/
@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class HelloSchedule {
@Async
@Scheduled(cron = "* * * * * ?")
public void hello(){
log.info("hello...");
try {
TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {
e.printStackTrace();}
}
}
结果:
定时任务调用异步进行,仍能做到每秒执行一次
2020-09-23 11:43:15.023 INFO 620 --- [ task-1] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:43:16.001 INFO 620 --- [ task-2] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:43:17.003 INFO 620 --- [ task-3] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:43:18.002 INFO 620 --- [ task-4] c.r.g.seckill.scheduled.HelloSchedule : hello...
2020-09-23 11:43:19.002 INFO 620 --- [ task-5] c.r.g.seckill.scheduled.HelloSchedule : hello...