Springboot整合实现定时任务和异步定时任务 解决定时任务阻塞问题 cron表达式解析

实现定时任务 Springboot整合

1.cron表达式

参考文章

在线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...

猜你喜欢

转载自blog.csdn.net/weixin_44634197/article/details/108750023