Spring Task 定时任务没有定时执行是为什么?

简介: Spring Task 定时任务没有定时执行是为什么?

在日常开发中,定时任务的应用已经变得十分常见,例如定时发送邮件、短信,数据定时转储等。然而,有时候我们会遇到定时任务没有按照预期执行的情况。本文将讨论 Spring Task 中定时任务执行的原理以及可能遇到的相关问题。

SpringBoot 定时任务的原理

绝大部分开发者在使用 Spring Boot 时都会使用它提供的定时任务 Starter 和定时任务的注解。因此,让我们首先了解一下 Spring Boot 实现定时任务的原理以及相关注解的作用。

自 Spring 3.0 版本以后,Spring 使用 @Scheduled 注解来支持定时任务。

在使用时,我们需要在应用的启动类上加上 @EnableScheduling 注解,该注解从 Spring 3.1 开始提供。

然而,在 Spring 3.0 版本之前,我们通常会通过 XML 配置和 @Scheduled 注解配合使用来实现定时任务。而 @EnableScheduling 注解实际上相当于该 XML 配置的简化版。它的作用是发现标记了 @Scheduled 注解的方法,没有该注解的方法将不会被执行。但是在大多数情况下,我们只需要关注目标实现,而不必深究其背后的细节。

任务一直阻塞会怎么样?

为了更好地理解定时任务的执行过程,我们进行了一个简单的实验:编写一个每隔 20 秒执行一次的定时任务,并在其中加入一个断点。

实验结果显示,当遇到断点时,后续的任务会被阻塞。这是因为线程池中只有一个线程用于执行任务,而该线程被阻塞后,后续任务将无法执行。线程池内部采用 DelayQueue 延迟队列实现,其特点是无界、延迟、阻塞,能够按照一定的顺序执行工作队列中的元素。

多个定时任务的执行

根据前面的实验结果,我们知道默认情况下,任务的线程池只有一个线程,因此多个定时任务应该是串行执行的。如果一个任务被阻塞,其他任务也会受到影响。

那么,如果我们将线程池的线程数设置为多个,会发生什么呢?实验结果显示,即使线程池中包含多个线程,多个定时任务仍然是串行执行的。只有在使用自定义的线程池,并且在任务上加上 @Async 注解时,才能实现并发执行。

具有相同表达式的定时任务,它们的执行顺序如何?

具有相同表达式的定时任务的执行顺序与调度有关。默认情况下,它们是串行执行的,首先获取到 CPU 时间片的任务会先执行。在多线程的情况下,具体的执行顺序取决于线程池的线程数、所用队列等因素。

Spring Task 和 Linux crontab 的 cron 语法区别?

两者的 cron 表达式非常相似,但需要注意的是,Linux 的 crontab 只提供了分钟级别的最小粒度,而 Spring Task 的最小粒度从秒开始。

在 cron 语法中容易犯的错误

在编写 cron 表达式时,很容易犯一些错误。例如,要求编写每十分钟执行一次的 cron 语句时,可能会写成类似以下的版本:

0 0/10 * * * ?

因此,在编写 cron 表达式后,建议适当降低执行间隔时间进行测试,或者使用在线工具检测 cron 脚本是否正确。

@Async 异步注解原理及作用

Spring Task 中与异步相关的注解有两个,一个是 @EnableAsync,另一个是 @Async。

默认情况下,@Async 注解会使方法仍然由一个线程同步执行。要想实现异步执行,需要在启动类上加上 @EnableAsync 注解,并定义一个线程池。

并发执行任务如何配置?

我们可以通过两种方式来配置并发执行任务:

  1. 将默认的线程池替换为自定义的线程池。
  2. 不改变任务调度器默认使用的线程池,而是将任务交给一个异步线程池去执行。

通过这些方式,我们可以让任务高效地并发执行,提高系统的性能。

方式一,我们可以将默认的线程池替换为我们自定义的线程池。通过ScheduleConfig配置文件实现SchedulingConfigurer接口,并重写setSchedulerfang方法。
可实现AsyncConfigurer接口复写getAsyncExecutor获取异步执行器,getAsyncUncaughtExceptionHandler获取异步未捕获异常处理器
@Configurationpublic
class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
    }
}
方式二:不改变任务调度器默认使用的线程池,而是把当前任务交给一个异步线程池去执行。
  @Scheduled(fixedRate = 1000*10,initialDelay = 1000*20)
  @Async("hyqThreadPoolTaskExecutor")
  public void test(){
      System.out.println(Thread.currentThread().getName()+"--->xxxxx--->"+Thread.currentThread().getId());
  }
  //自定义线程池
  @Bean(name = "hyqThreadPoolTaskExecutor")
  public TaskExecutor  getMyThreadPoolTaskExecutor() {
      ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
      taskExecutor.setCorePoolSize(20);
      taskExecutor.setMaxPoolSize(200);
      taskExecutor.setQueueCapacity(25);
      taskExecutor.setKeepAliveSeconds(200);
      taskExecutor.setThreadNamePrefix("hyq-threadPool-");
      taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
      taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
      taskExecutor.setAwaitTerminationSeconds(60);
      taskExecutor.initialize();
      return taskExecutor;

其他问题

  • 如果定时任务没有生效,需要检查是否加上了 @EnableScheduling 注解。
  • 如果异步任务没有生效,需要检查是否加上了 @EnableAsync 注解,并且定义了线程池,否则仍然是串行执行的。

总结

本文介绍了 Spring Boot 定时任务的原理,通过实验探究了延迟队列及串行、并行、并发的概念。对比了 Linux 下的 crontab 和 Spring 的 cron 表达式区别以及常见的错误。最后通过实验异步注解,两种方式配置线程池,使任务能够高效运行。希望本文能够对你有所帮助。

相关文章
|
2月前
|
druid Java 数据库
Spring Boot的定时任务与异步任务
Spring Boot的定时任务与异步任务
|
2月前
|
Java 数据库 Spring
Spring Boot 实现定时任务的动态增删启停
Spring Boot 实现定时任务的动态增删启停
49 0
|
2月前
|
Java 调度 Maven
Spring Task 自定义定时任务类
Spring Task 自定义定时任务类
45 0
|
2月前
|
NoSQL Java Redis
Spring Boot 监听 Redis Key 失效事件实现定时任务
Spring Boot 监听 Redis Key 失效事件实现定时任务
78 0
|
4天前
|
监控 Java BI
Spring Boot中的定时任务调度
Spring Boot中的定时任务调度
|
4天前
|
Java Spring
解密Spring Boot的定时任务
解密Spring Boot的定时任务
4 0
|
4天前
|
监控 Java 调度
Spring Boot中的定时任务调度
Spring Boot中的定时任务调度
|
8天前
|
Java 调度 开发者
Spring Task
Spring Task
9 0
|
2月前
|
Java 调度 开发者
Spring Boot与定时任务:整合与实战
【4月更文挑战第29天】定时任务是现代应用中常见的需求,用于执行周期性的活动,如数据备份、报告生成等。Spring Boot通过集成Spring Task的功能,提供了一种简单有效的方式来调度和执行定时任务。
35 1
|
2月前
|
Java API 调度
定时任务spring task
定时任务是自动化处理任务的关键,常见于电商平台的优惠券发放、银行的还款提醒、财务系统的每日结算和火车票定时放票等场景。实现方式包括多线程结合sleep、JDK的Timer和ScheduledExecutor,以及Quartz和Spring Task等框架。Cron表达式用于设定任务执行的时间规则,由7部分组成,表示秒、分、时、日、月、周和年,并可使用特殊符号如*、?、-、/等来设定更灵活的定时规则。
58 9