1、背景
- 上线前测试发现定时任务跑着跑着停止了,其他定时任务正常运行,如商品同步、店铺信息同步等均正常运行。
- 检查结果:
1.代码没错误;
2.重启服务定时任务又正常执行。
2.本地问题测试
@Component
@Slf4j
public class SingleScheduledTest {
@Scheduled(cron = "0/5 * * * * ?")
public void test1(){
String threadName = Thread.currentThread().getName();
log.info("定时任务执行1,当前时间:{},当前线程名称:{}", LocalDateTime.now(),threadName);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Scheduled(cron = "0/5 * * * * ?")
public void test2(){
String threadName = Thread.currentThread().getName();
log.info("定时任务执行2,当前时间:{},当前线程名称:{}", LocalDateTime.now(),threadName);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 代码运行结果
定时任务执行1,当前时间:2024-07-30T15:45:55.005,当前线程名称:scheduling-1
定时任务执行2,当前时间:2024-07-30T15:45:56.013,当前线程名称:scheduling-1
定时任务执行2,当前时间:2024-07-30T15:46:00.011,当前线程名称:scheduling-1
定时任务执行1,当前时间:2024-07-30T15:46:01.019,当前线程名称:scheduling-1
定时任务执行2,当前时间:2024-07-30T15:46:05.009,当前线程名称:scheduling-1
定时任务执行1,当前时间:2024-07-30T15:46:06.017,当前线程名称:scheduling-1
很明显,定时任务间隔为5s不符合初始规定的要求,而且并不是多线程,此时大概率可以确定为线程捣的鬼。
3.追源码
流程概述:
- Spring 容器启动:
Spring 容器启动时,ScheduledAnnotationBeanPostProcessor 会扫描所有的 @Scheduled 注解。 - 注册任务:
ScheduledAnnotationBeanPostProcessor 会将带有 @Scheduled 注解的方法注册到 ScheduledTaskRegistrar 中。 - 执行 scheduleTasks 方法:
ScheduledTaskRegistrar 的 scheduleTasks 方法会被调用,以初始化调度器并注册任务。
由此可见,默认情况下,会注册一个newSingleThreadScheduledExecutor(单线程)。
4.解决问题
经验法则:
- CPU 密集型任务:
int poolSize = Runtime.getRuntime().availableProcessors(); - I/O 密集型任务:
int poolSize = Runtime.getRuntime().availableProcessors() * 2;
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(Runtime.getRuntime().availableProcessors() * 2);
return taskScheduler;
}
代码执行结果:
定时任务执行2,当前时间:2024-07-30T18:18:25.002,当前线程名称:taskScheduler-1
定时任务执行1,当前时间:2024-07-30T18:18:25.002,当前线程名称:taskScheduler-2
定时任务执行2,当前时间:2024-07-30T18:18:30.010,当前线程名称:taskScheduler-2
定时任务执行1,当前时间:2024-07-30T18:18:30.010,当前线程名称:taskScheduler-1
定时任务执行2,当前时间:2024-07-30T18:18:35.006,当前线程名称:taskScheduler-4
定时任务执行1,当前时间:2024-07-30T18:18:35.006,当前线程名称:taskScheduler-3
定时任务执行1,当前时间:2024-07-30T18:18:40.005,当前线程名称:taskScheduler-2
定时任务执行2,当前时间:2024-07-30T18:18:40.005,当前线程名称:taskScheduler-1
- 其他的解决方案:
采用异步@Async,但是如果采用默认单线程,前一个任务阻塞,后面任务仍无法进行,有待研究。