关于@Scheduled注解的任务为什么不执行的问题
概述
在SpringBoot中可以通过@Scheduled来注解定义一个定时任务,但是有时候你可能发现有的定时任务道理时间却没有执行,但是又不是每次都不执行,为什么呢???
举例说明
下面这段diam定义了一个没隔10s执行一次的定时任务:
package com.study.practice.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class ScheduleTest {
@Scheduled(cron = "0/10 * * * * ?")
public void execute() {
log.info("Scheduled task is running ... ...");
}
}
记得在启动类或者Configuration类上添加了@EnableScheduling注解。
启动应用,控制台每隔10秒打印一条日志:
2021-12-01 11:27:00.002 INFO 97876 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:27:10.001 INFO 97876 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:27:20.002 INFO 97876 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:27:30.001 INFO 97876 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:27:40.002 INFO 97876 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:27:50.001 INFO 97876 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:28:00.002 INFO 97876 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:28:10.002 INFO 97876 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
但是,问题才刚刚开始。
在上面的相关代码中,我们使用cron表达式指定的定时任务执行时间点从0秒开始,每隔10s执行一次,现在我们再加一个定时任务:
package com.study.practice.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class ScheduleTestTwo {
@Scheduled(cron = "0/10 * * * * *")
public void second() throws InterruptedException {
log.info("Second scheduled task is running... ...");
TimeUnit.SECONDS.sleep(5);
}
}
启动项目,执行结果如下:
2021-12-01 11:36:30.001 INFO 134576 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:36:30.002 INFO 134576 --- [ scheduling-1] c.s.practice.schedule.ScheduleTestTwo : Second scheduled task is running... ...
2021-12-01 11:36:40.002 INFO 134576 --- [ scheduling-1] c.s.practice.schedule.ScheduleTestTwo : Second scheduled task is running... ...
2021-12-01 11:36:45.002 INFO 134576 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:36:50.001 INFO 134576 --- [ scheduling-1] c.s.practice.schedule.ScheduleTestTwo : Second scheduled task is running... ...
2021-12-01 11:36:55.002 INFO 134576 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 11:37:00.001 INFO 134576 --- [ scheduling-1] c.s.practice.schedule.ScheduleTestTwo : Second scheduled task is running... ...
2021-12-01 11:37:05.001 INFO 134576 --- [ scheduling-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
可以看到任务1和任务2本该都是每个10s执行,但是却发现只有任务2执行了,任务1却等待了5s之后才执行。
原因分析
为了找到原因,我们从@Scheduled注解的源码开始分析:
下面是@Scheduled注解的注释:
*
* <p>Processing of {@code @Scheduled} annotations is perfORMed by
* reGIStering a {@link ScheduledAnnotationBeanPostProcessor}. This can be
* done manually or, more conveniently, through the {@code <task:annotation-driven/>}
* element or @{@link EnableScheduling} annotation.
*
划重点, 每一个有@Scheduled注解的方法都会被注册为一个ScheduledAnnotationBeanPostProcessor, 再接着往下看ScheduledAnnotationBeanPostProcessor:
public void setScheduler(Object scheduler) {
this.scheduler = scheduler;
}
重点来了,注意这句话:
if neither of two is resolvable, a local single-thread default scheduler will be created within in the registrar.
这句话意味着,如果我们不主动配置我们需要的TaskScheduler,SpringBoot会默认使用一个单线程的scheduler来处理我们用@Scheduled注解实现的定时任务,到此我们刚才的问题就可以理解了。
因为是单个线程执行所有的定时任务,所有task2如果先执行,因为执行中等待了5s,所以task2执行完后,task1接着继续执行。
解决方案
搞清楚这个流程后,解决这个问题就很简单了。
根据刚才注释的描述,我们只需要提供一个满足自己需要的TaskScheduler并注册到context容器中就可以了。
package com.study.practice.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulinGConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@Configuration
public class ScheduledTaskConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(2);
taskScheduler.initialize();
taskRegistrar.setTaskScheduler(taskScheduler);
}
}
上面的代码提供了一个线程池大小为2的taskScheduler,再次启动SpringBoot查看效果。
2021-12-01 14:05:20.001 INFO 39768 --- [TaskScheduler-2] c.s.practice.schedule.ScheduleTestTwo : Second scheduled task is running... ...
2021-12-01 14:05:20.001 INFO 39768 --- [TaskScheduler-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 14:05:30.002 INFO 39768 --- [TaskScheduler-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 14:05:30.002 INFO 39768 --- [TaskScheduler-2] c.s.practice.schedule.ScheduleTestTwo : Second scheduled task is running... ...
2021-12-01 14:05:40.001 INFO 39768 --- [TaskScheduler-1] c.study.practice.schedule.ScheduleTest : Scheduled task is running ... ...
2021-12-01 14:05:40.001 INFO 39768 --- [TaskScheduler-2] c.s.practice.schedule.ScheduleTestTwo : Second scheduled task is running... ...
可以看到,当线程池里有两个线程时,这两个任务各种按照预定的时间进行触发,互不影响了。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
相关文章