scheduledexecutorservice 的使用_Spring Boot 中使用 Spring Task 实现定时任务

推荐阅读:

面试“阿里云”,竟被 Spring 聊晕了,还好4面顺利拿下offer

阿里技术大牛的Spring秘籍:Spring+SpringBoot+SpringCloud等

阿里架构师玩转spring全家桶(实战篇),附赠3本spring电子书

20257e0f6134745c681d599a85237faa.png

1. 前言

在日常项目开发中我们经常要使用定时任务。比如在凌晨进行统计结算,开启策划活动等等。今天我们就来看看如何在 Spring Boot 中使用 Spring 内置的定时任务。

2. 开启定时任务

Spring Boot 默认在无任何第三方依赖的情况下使用 spring-context 模块下提供的定时任务工具 Spring Task。我们只需要使用 @EnableScheduling 注解就可以开启相关的定时任务功能。如:

package cn.felord.schedule; import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableScheduling; /** * @author felord.cn */@SpringBootApplication@EnableSchedulingpublic class SpringbootScheduleApplication {     public static void main(String[] args) {        SpringApplication.run(SpringbootScheduleApplication.class, args);    } }

然后我们就可以通过注解的方式实现自定义定时任务,下面我将详细介绍如何使用注解实现定时任务。

3. @Scheduled 注解实现定时任务

只需要定义一个 Spring Bean ,然后定义具体的定时任务逻辑方法并使用 @Scheduled 注解标记该方法即可。

package cn.felord.schedule.task; import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component; import java.time.LocalDateTime;import java.time.format.DateTimeFormatter; /** * @author felord.cn * @since 11:02 **/@Componentpublic class TaskService {     @Scheduled(fixedDelay = 1000)    public void task() {        System.out.println("Thread Name : "                    + Thread.currentThread().getName() + "  i am a task : date ->  "                    + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));     }}

请注意:@Scheduled 注解中一定要声明定时任务的执行策略 cron 、fixedDelay、fixedRate 三选一。

我们来认识一下 @Scheduled 提供了四个属性。

3.1 cron 表达式

cron。这个我们已经在上一篇文章 详解定时任务中的 CRON 表达式[1] 中详细介绍,这里不再赘述。

3.2 fixedDelay

fixedDelay。它的间隔时间是根据上次的任务结束的时候开始计时的,只要盯紧上一次执行结束的时间即可,跟任务逻辑的执行时间无关,两个轮次的间隔距离是固定的。

751988977344a536044948d3fc10e4dd.png

3.3 fixedRate

fixedRate。这个相对难以理解一些。在理想情况下,下一次开始和上一次开始之间的时间间隔是一定的。但是默认情况下 Spring Boot 定时任务是单线程执行的。当下一轮的任务满足时间策略后任务就会加入队列,也就是说当本次任务开始执行时下一次任务的时间就已经确定了,由于本次任务的“超时”执行,下一次任务的等待时间就会被压缩甚至阻塞,算了画张图就明白了。

3.4 initialDelay

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

4. Spring Task 的弊端

Spring Task 在实际应用中如果不明白一些机制会出现一些问题的,所以下面的一些要点十分重要。

4.1 单线程阻塞执行

从 3.3 章节 我们知道 Spring 的定时任务默认是单线程执行,多任务情况下,如果使用多线程会影响定时策略。我们来演示一下:

package cn.felord.schedule.task; import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component; import java.time.LocalDateTime;import java.time.format.DateTimeFormatter; /** * The type Task service. * * @author felord.cn * @since 11 :02 */@Componentpublic class TaskService {      /**     * 上一次任务结束后 1 秒,执行下一次任务,任务消耗 5秒     *     * @throws InterruptedException the interrupted exception     */    @Scheduled(fixedDelay = 1000)    public void task() throws InterruptedException {        System.out.println("Thread Name : "                + Thread.currentThread().getName()                + "  i am a task : date ->  "                + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));        Thread.sleep(5000);    }     /**     * 下轮任务在本轮任务开始2秒后执行. 执行时间可忽略不计     */    @Scheduled(fixedRate = 2000)    public void task2() {        System.out.println("Thread Name : "                + Thread.currentThread().getName()                + "  i am a task2 : date ->  "                + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));    } }

上面定义了两个定时任务(策略参见注释),运行结果如下:

 Thread Name : scheduling-1  i am a task2 : date ->  2020-01-13 17:16:19 Thread Name : scheduling-1  i am a task : date ->  2020-01-13 17:16:19 Thread Name : scheduling-1  i am a task2 : date ->  2020-01-13 17:16:24 Thread Name : scheduling-1  i am a task2 : date ->  2020-01-13 17:16:24 Thread Name : scheduling-1  i am a task2 : date ->  2020-01-13 17:16:25 Thread Name : scheduling-1  i am a task : date ->  2020-01-13 17:16:25

转换为图形比较好理解上面日志的原因:

5e1364dae43b84ca4b1416fbc53bace6.png

也就是说因为单线程阻塞发生了“连锁反应”,导致了任务执行的错乱。如果你准备用定时任务打算开启 “11.11” 活动,岂不是背锅的节奏。为了不背锅我们就需要改造定时任务的机制。@EnableScheduling 注解引入了 ScheduledAnnotationBeanPostProcessor 其 setScheduler(Object scheduler) 有以下的注释:

如果 TaskScheduler 或者 ScheduledExecutorService 没有定义为该方法的参数,该方法将在 Spring IoC 中寻找唯一的 TaskScheduler 或者 名称为 taskScheduler 的 Bean 作为参数,当然你按照查找 TaskScheduler 的方法找一个ScheduledExecutorService 也可以。要是都找不到那么只能使用本地单线程调度器了。

Spring Task 的调用顺序关系为:任务调度线程 调度 任务执行线程 执行 定时任务 所以我们按照上面定义一个 TaskScheduler 在 Spring Boot 自动配置中提供了 TaskScheduler 的自动配置:

@ConditionalOnClass(ThreadPoolTaskScheduler.class)

@Configuration(proxyBeanMethods = false)

@EnableConfigurationProperties(TaskSchedulingProperties.class)

@AutoConfigureAfter(TaskExecutionAutoConfiguration.class)

public class TaskSchedulingAutoConfiguration {

@Bean

@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)

@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })

public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {

return builder.build();

}

@Bean

@ConditionalOnMissingBean

public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,

ObjectProvider taskSchedulerCustomizers) {

TaskSchedulerBuilder builder = new TaskSchedulerBuilder();

builder = builder.poolSize(properties.getPool().getSize());

Shutdown shutdown = properties.getShutdown();

builder = builder.awaitTermination(shutdown.isAwaitTermination());

builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());

builder = builder.threadNamePrefix(properties.getThreadNamePrefix());

builder = builder.customizers(taskSchedulerCustomizers);

return builder;

}

}

该配置的自定义配置以 spring.task.scheduling 开头。同时它需要在任务执行器配置 TaskExecutionAutoConfiguration 配置后才生效。我们只需要在中对其配置属性 spring.task.execution 相关属性配置即可。

Spring Boot 的 application.properties 中相关的配置说明:

# 任务调度线程池 # 任务调度线程池大小 默认 1 建议根据任务加大spring.task.scheduling.pool.size=1# 调度线程名称前缀 默认 scheduling-spring.task.scheduling.thread-name-prefix=scheduling-# 线程池关闭时等待所有任务完成spring.task.scheduling.shutdown.await-termination=# 调度线程关闭前最大等待时间,确保最后一定关闭spring.task.scheduling.shutdown.await-termination-period=  # 任务执行线程池配置 # 是否允许核心线程超时。这样可以动态增加和缩小线程池spring.task.execution.pool.allow-core-thread-timeout=true#  核心线程池大小 默认 8spring.task.execution.pool.core-size=8# 线程空闲等待时间 默认 60sspring.task.execution.pool.keep-alive=60s# 线程池最大数  根据任务定制spring.task.execution.pool.max-size=#  线程池 队列容量大小spring.task.execution.pool.queue-capacity=# 线程池关闭时等待所有任务完成spring.task.execution.shutdown.await-termination=true# 执行线程关闭前最大等待时间,确保最后一定关闭spring.task.execution.shutdown.await-termination-period=# 线程名称前缀spring.task.execution.thread-name-prefix=task-

配置完后你就会发现定时任务可以并行异步执行了。

4.2 默认不支持分布式

Spring Task 并不是为分布式环境设计的,在分布式环境下,这种定时任务是不支持集群配置的,如果部署到多个节点上,各个节点之间并没有任何协调通讯机制,集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行,导致任务的重复执行。我们可以使用支持分布式的定时任务调度框架,比如 Quartz、XXL-Job、Elastic Job。当然你可以借助 zookeeper 、 redis 等实现分布式锁来处理各个节点的协调问题。或者把所有的定时任务抽成单独的服务单独部署。

5. 总结

今天我们对 Spring Task 在 Spring Boot 中的应用进行简单的了解。分析了定时任务的策略机制、对多任务串行引发的问题的分析以及如何使得多任务并行异步执行。还对分布式下定时任务的一些常用解决方案进行了列举。希望对你在使用 Spring Task 的过程中有所帮助, 原创技术干货请认准:felord.cn[2] 。

作者:码农小胖哥

转载自:https://blog.csdn.net/qq_35067322/article/details/103982304

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Spring BootScheduledExecutorService是一个Java定时器工具类,可以用于在Spring Boot应用程序启动时启动和运行任务。你可以使用自定义的定时器工具类,结合Spring Boot一起使用。另外,你也可以使用ScheduledExecutorService接口提供的方法来创建一个定时任务执行线程池。例如,在Spring Boot,你可以通过@Bean注解来创建一个ScheduledExecutorService实例,并将其注入到需要使用的地方。这样,你就可以使用这个线程池来执行周期性的定时任务。如果在Spring Boot找不到默认的ScheduledExecutorService实例,系统将会抛出NoSuchBeanDefinitionException异常,并打印出相应的错误信息。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [自定义Java-ScheduledExecutorService定时器.jar](https://download.csdn.net/download/wsk1103/10230495)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [scheduledexecutorservice使用_SpringBoot @Schedule使用注意与原理](https://blog.csdn.net/weixin_39574869/article/details/110998360)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [spring boot.定时任务问题记录(TaskScheduler/ScheduledExecutorService异常)](https://blog.csdn.net/acj2008/article/details/101941093)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值