Spring定时任务高级使用篇

简介: 前面一篇博文 《Spring之定时任务基本使用篇》 介绍了Spring环境下,定时任务的简单使用姿势,也留了一些问题,这一篇则希望能针对这些问题给个答案

image.png


前面一篇博文 《Spring之定时任务基本使用篇》 介绍了Spring环境下,定时任务的简单使用姿势,也留了一些问题,这一篇则希望能针对这些问题给个答案


I. 定时任务进阶篇



1. 问题小结


前面一篇博文,抛出了下面的几个问题,接下来则围绕问题进行分析


  • 一个项目中有多个定时任务时,他们是并行执行的还是串行执行的?
  • 如果默认是串行的
  • 那么有相同的crond表达式的定时任务之间,有先后顺序么?
  • 某个任务的阻塞是否会影响后面的任务?
  • 如果需要他们并行执行,可以怎么做?
  • 如果是并发执行的
  • 是新创建线程还是采用线程池来复用呢?
  • 在并发执行时,假设有个每秒执行一次的任务,但是它执行一次消耗的时间大于1s时,这个任务的表现时怎样的呢?不断地新增线程来执行还是等执行完毕之后再执行下一次的呢?


2. 多定时任务的串并行分析


如何确认一个项目中的多个定时任务是串行执行还是并发执行呢?要想验证这个功能,最好的法子就是写个testcase,比如定义两个定时任务,在其中一个任务中写个死循环,看另外一个任务是否会正常执行


@Scheduled(cron = "0/1 * * * * ?")
public void sc1() throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + " | sc1 " + System.currentTimeMillis());
    while (true) {
        Thread.sleep(5000);
    }
}
@Scheduled(cron = "0/1 * * * * ?")
public void sc2() {
    System.out.println(Thread.currentThread().getName() + " | sc2 " + System.currentTimeMillis());
}
复制代码


首先我们分析的是 sc1和sc2这两个任务的执行是串行还是并行的,暂时先不考虑 sc1 调用时阻塞,下一秒是否是开新的线程再调用sc1


  • 若串行:则sc1打印一次,sc2可能打印0或者1次
  • 若并行:sc1打印一次,sc2打印n多次


实际运行,GIF图演示如下

image.png


上图的结果,印证了默认的情况下,多个定时任务时串行执行的;如果一个任务出现阻塞,其他的任务都会受到影响


3. 定时任务执行的优先级


既然是顺序执行的,那么优先级怎么定?每次都是固定的,还是随机的呢?


要验证上面的方法,也容易,同样两个任务,看他们的输出是否会乱掉,如果每次都是任务1打印完再打印任务2,那就是固定优先级的;否则每次调度时,顺序不好说

测试代码如下


@Scheduled(cron = "0/1 * * * * ?")
public void sc1()  {
    System.out.println(Thread.currentThread().getName() + " | sc1 " + System.currentTimeMillis());
}
@Scheduled(cron = "0/1 * * * * ?")
public void sc2() {
    System.out.println(Thread.currentThread().getName() + " | sc2 " + System.currentTimeMillis());
}
复制代码


实测结果如下

image.png


从输出得出结论:顺序是串掉的,并没有表现出明显的优先级关系


4. 并行调度


接下来的问题就是我希望这些任务可以并发执行,可以实现么?


当然是可以,用起来也比较简单,首先是在Application上添加注解@EnableAsync,开启异步调用,然后再计划任务上加上@Async注解即可,一个简单的demo如下

@EnableAsync
@EnableScheduling
@SpringBootApplication
public class QuickMediaApplication {
    public static void main(String[] args) {
        SpringApplication.run(QuickMediaApplication.class, args);
    }
    @Scheduled(cron = "0/1 * * * * ?")
    @Async
    public void sc1()  {
        System.out.println(Thread.currentThread().getName() + " | sc1 " + System.currentTimeMillis());
    }
}
复制代码


上面执行之后,查看输出(异步调度时,理论上线程名应该不一样)

image.png


从上面的输出,可以简单的推理,每次调度上面的任务都是新开了一个线程来做的,所以如果在定时任务中写了死循环,是否会导致无限线程,最后整个进程崩掉?


额外提一句,linux系统下单进程的线程数是有上线的,查看命令为:

ulimit -u
复制代码


在测试之前,先看下上面的正常任务执行,如下面的动图,线程数并没有夸张的长法

image.png


接下来换成死循环的调度方式,实际测试如下,线程数蹭蹭的上涨

image.png


所以使用默认的异步调用方式,并不是一个好注意,说不准就被玩死了自己都不知道,那么可以用自己的线程池来管理这些异步任务么?


5. 自定义线程池


用自定义的线程池来取代默认线程管理方式,无疑是一个更加安全和灵活的方式,使用起来也并不麻烦,和平常创建线程池的套路没什么区别,要在Spring生态中使用,就把它搞成bean即可


直接借助Spring的线程池ThreadPoolTaskExecutor


@Bean
public AsyncTaskExecutor asyncTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setThreadNamePrefix("yhh-schedule-");
    executor.setMaxPoolSize(10);
    executor.setCorePoolSize(3);
    executor.setQueueCapacity(0);
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    return executor;
}
@Scheduled(cron = "0/1 * * * * ?")
@Async
public void sc1() throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + " | sc1 " + System.currentTimeMillis());
    while (true) {
        Thread.sleep(1000 * 5);
    }
}
复制代码


实际演示的结果如下,最多10个线程,再提交的任务直接丢弃

image.png


简单说一下,用自定义线程池的好处:


  • 合理的分配线程池参数
  • 拒绝策略的选择也比较有意思(可以按照自己的想法来处理"负载"的任务)
  • 线程池命名,对于以后问题排查,会有很大的帮助


6. 小结


本来这篇博文在昨天即8月2号就应该写完的,结果晚上生产环境下除了点问题,解决线上故障之后就比较晚了,留到了今天,哎,拖延症也是要不得。。。


下面小结Spring中定时任务的几个知识点


  • 默认所有的定时任务都是串行调度的,一个线程,且即便crond完全相同的两个任务先后顺序也没法保证(具体原因需要源码分析,看下这块是怎么支持)
  • 使用@Async注解可以使定时任务异步调度;但是需要开启配置,在启动类上添加 @EnableAsync 注解
  • 开启并发执行时,推荐用自定义的线程池来替代默认的,理由见上面



相关文章
|
4月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
138 1
|
2月前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
154 1
|
2月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
143 4
|
5月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
15037 33
|
4月前
|
Java 开发者 Spring
Spring Boot实战宝典:揭秘定时任务的幕后英雄,让业务处理如流水般顺畅,轻松驾驭时间管理艺术!
【8月更文挑战第29天】在现代应用开发中,定时任务如数据备份、报告生成等至关重要。Spring Boot作为流行的Java框架,凭借其强大的集成能力和简洁的配置方式,为开发者提供了高效的定时任务解决方案。本文详细介绍了如何在Spring Boot项目中启用定时任务支持、编写定时任务方法,并通过实战案例展示了其在业务场景中的应用,同时提供了注意事项以确保任务的正确执行。
56 0
|
4月前
|
Dubbo Java 调度
揭秘!Spring Cloud Alibaba的超级力量——如何轻松驾驭分布式定时任务调度?
【8月更文挑战第20天】在现代微服务架构中,Spring Cloud Alibaba通过集成分布式定时任务调度功能解决了一致性和可靠性挑战。它利用TimerX实现任务的分布式编排与调度,并通过`@SchedulerLock`确保任务不被重复执行。示例代码展示了如何配置定时任务及其分布式锁,以实现每5秒仅由一个节点执行任务,适合构建高可用的微服务系统。
72 0
|
5月前
|
SQL Java 调度
实时计算 Flink版产品使用问题之使用Spring Boot启动Flink处理任务时,使用Spring Boot的@Scheduled注解进行定时任务调度,出现内存占用过高,该怎么办
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
5月前
|
XML Java Linux
Spring Task 定时任务没有定时执行是为什么?
Spring Task 定时任务没有定时执行是为什么?
78 2
|
6月前
|
前端开发 Java 开发者
深入理解 Spring Boot 注解:核心功能与高级用法详解
深入理解 Spring Boot 注解:核心功能与高级用法详解
363 1
|
6月前
|
Java Spring
解密Spring Boot的定时任务
解密Spring Boot的定时任务
29 0