• 【开发篇】十五、Spring Task实现定时任务


    上一篇用Quartz来实现了定时任务,但相对来说,这个框架还是比较繁琐。Spring Boot默认在无任何第三方依赖的情况下使用Spring-context模块下提供的定时任务工具 Spring Task

    1、使用示例

    @EnableScheduling开启定时任务功能:

    @SpringBootApplication
    
    @EnableScheduling
    
    public class TaskApplication {   
    
    	public static void main(String[] args) {  
    	      
    		SpringApplication.run(TaskApplication.class, args); 
    		   
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    接下来就可以通过@Scheduled注解方式自定义定时任务:

    @Component
    public class ScheduledBean {    
    
    	@Scheduled(cron = "0/5 * * * * ?")    
    	public void printLog(){  
    	      
    		System.out.println(Thread.currentThread().getName()+":run...");  
    		 
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    重启服务:

    在这里插入图片描述

    2、相关配置

    以上是基本使用,还可配置一些通用配置,如下:

    spring:  
      task:    
        scheduling:      
          # 任务调度线程池大小,因为要开线程异步,最多能开几个线程?,默认 1     
          pool:       
            size: 1      
          # 调度线程名称前缀 默认 scheduling-,程序线程一多,不好找,加了它可读性高     
          thread-name-prefix: ssm_     
          shutdown:        
            # 线程池关闭时,是否等待所有任务完成        
            await-termination: false        
            # 调度线程关闭前最大等待时间,确保最后一定关闭(如果等待,最多等几秒)        
            await-termination-period: 10s
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意thread-name-prefix配置,是日志里定时任务的线程的前缀,默认为scheduling-num,num即序号。以上这个配置由TaskSchedulingProperties类从yaml中接收。

    在这里插入图片描述

    3、@Scheduled注解

    整理下@Scheduled注解的常用属性:

    • cron,cron表达式,语法跳这篇【cron表达式语法】
    • fixedDelay,固定时延。从上次任务执行结束的时间开始,到下个任务开始的时间间隔。不关心任务逻辑、任务本身执行多长时间。下图为fixedDelay为4s时的示意图:

    在这里插入图片描述

    • fixedRate,固定频率。在理想情况下,下一次开始和上一次开始之间的时间间隔是一定的,但当如果上一次任务因为其他原因超时好久,而pool.size的默认值为1,即默认情况下 Spring Boot 定时任务是单线程执行的,那下一轮任务就会被阻塞。类比地铁每隔10分钟发一列,也就是说所有列车其实已经安排好了时刻表,理想情况下,每列车准点发就行了,互不影响,但是如果其中一列晚点,那么就会导致下一列晚点。

    图片来源cloud.tencent.com/developer/article/1582434

    • initialDelay:初始化延迟时间,也就是第一次延迟执行的时间。这个参数对 cron 属性无效,只能配合 fixedDelay 或 fixedRate 使用。如 @Scheduled(initialDelay=5000,fixedDelay = 1000) 表示第一次延迟 5000 毫秒执行,下一次任务在上一次任务结束后 1000 毫秒后执行。

    fixedDelay和fixedRate,都是和两轮任务有关,但前者关注的是第一轮的结束时间和第二轮的开始时间的这个间隔。而后者关注的都是两轮的开始时间中间的这个间隔。

    4、Spring Task单线程下的阻塞坑

    demo代码,演示两个任务在单线程下的阻塞:

    @Component
    public class ScheduleTask {
    
        /**
         * 上一次任务执行完后,歇一秒,再执行下一轮
         * 执行一次任务耗时5秒
         */
        @Scheduled(fixedDelay = 1000)
        public void task1() throws InterruptedException {
        
            System.out.println(Thread.currentThread().getName()
                    + "==>  spring task 1 ==> "
                    + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS"))
            );
                    
            Thread.sleep(5000);
        }
    
        /**
         * 下轮任务在上一轮任务开始后2秒执行.
         * 执行一次任务耗时可忽略
         */
        @Scheduled(fixedRate = 2000)
        public void task2() {
            System.out.println(Thread.currentThread().getName()
                    + "==>  spring task2 ==> "
                    + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS"))
            );
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    执行效果:

    在这里插入图片描述

    可以看到task2被连续执行三次,且不妙的是两次任务开始时间没有间隔2s。这就是单线程下阻塞导致的问题,task1执行的5秒内,task2按预定的间隔触发的任务被阻塞,等task1一执行完,就会立刻执行这些阻塞的任务。这个延迟和堆积在生产中还是很严重的。

    5、Spring Task阻塞问题的处理思路

    第一种是直接改配置文件:

    既然问题在单线程,一个线程处理不过来而导致的问题,那让定时任务的执行改为多线程就行了:

    pring:  
      task:    
        scheduling:      
          # 任务调度线程池大小,因为要开线程异步,最多能开几个线程?,默认 1     
          pool:       
            size: 1     
            # 像上面的demo,设size为2即可 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    第二种是定义配置类,实现SchedulingConfigurer接口,设置taskScheduler:

    @Configuration
    public class ScheduleConfig implements SchedulingConfigurer {
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
            //设定一个长度10的定时任务线程池,这个大小自己判断
            taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    以下是这种的方式之所以能实现的源码解析:
    
    • 1

    可以从@EnableScheduling注解源码开始分析得出,先看这个注解导入的SchedulingConfiguration类:

    在这里插入图片描述

    返回一个调度注解Bean的后置处理器:

    在这里插入图片描述

    往下跟这个后置处理器的构造方法,看到了ScheduledTaskRegistrar,

    在这里插入图片描述

    往下跟也就看到了为什么Spring Task默认是单线程的,这里new的是一个单线程的调度执行器:

    在这里插入图片描述

    而上面自己写配置类,setScheduler即可跳过上面的默认单线程配置。

    第三种是加@Async注解开启异步任务

    启动类加@EnableAsyn开启注解支持c,在定时任务方法上加入注解@Async

    @Async
    @Schedule(...)
    public void task1(){
    	//...
    }
    
    
    @Async
    @Schedule(...)
    public void task2(){
    	//...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果有@Async这个注解的额外配置需求,参考:

    //非必须,看自己需求
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
        poolTaskExecutor.setCorePoolSize(4);
        poolTaskExecutor.setMaxPoolSize(6);
        // 设置线程活跃时间(秒)
        poolTaskExecutor.setKeepAliveSeconds(120);
        // 设置队列容量
        poolTaskExecutor.setQueueCapacity(40);
        poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        return poolTaskExecutor;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    6、Spring Task在分布式环境中

    在这里插入图片描述

  • 相关阅读:
    Xss跨站脚本攻击
    【Windows 常用工具系列 11 -- 笔记本F5亮度调节关闭】
    谈一谈MySQL 的索引机制以及优化建议
    [C++][python]python setup报错fatal error C1034: vector: 不包括路径集
    flutter(学习日记篇-1)
    干货 | 测试人职场晋升“潜规则”:15 年经验资深测试经理的职场忠告
    xilinx的原语的使用
    Linux下的yum和vim
    MindSpore易点通·精讲系列–网络构建之Conv2d算子
    Stable Diffusion 系统教程 | 强大的ControlNet 控制网
  • 原文地址:https://blog.csdn.net/llg___/article/details/133578754