文章目录
- 前言
- 背景
- 验证
- 解决方案
前言
在 Java Spring 项目中经常会用 @Scheduled
来实现一些定时任务的场景,有必要了解一些它使用时的问题和内部实现机制。本文是偶然间发现的一个问题,刷新了我的认知,分享给大家。
其他相关文章:Spring @Scheduled 多线程配置
背景
在 Spring Web 项目中,使用了多个 @Scheduled
来做任务的定时跑批,发现与预期的效果不一致
比如三个 @Scheduled
定时任务,在三个不同的类里面
- 任务1(class1):每10秒钟执行一次
- 任务2(class2):每10秒钟执行一次
- 任务3(class3):每10秒钟执行一次
预期效果:
三个任务是三个独立的定时任务线程池在控制,每10秒钟执行一次,任务之间互不影响
实际效果:
项目启动之后,发现这三个任务是串行执行,第一个任务未执行完的情况下,其他两个任务也会等待。
验证
启动项目之后,分别在三个任务上打上断点
spring @Scheduled 注解的处理类源码:
- org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor
- org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks 初始化方法
在项目启动过程中只进来了一次,taskScheduler 实例化赋值一次,而且是单线程的线程池 SingleThreadScheduledExecutor
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
观察 thread 名称,分别在每个 @Scheduled
方法中都打断点
第一个 FaceTask#start() 方法,threadName = pool-8-thread-1
第二个 ProfileTask#start() 方法,threadName = pool-8-thread-1
第三个 TmpFaceTask#faceDelayPush() 方法,threadName = pool-8-thread-1
通过源码+代码调试可以得到结论,在同一个 Spring 容器中(项目中),使用多个 @Scheduled 定时任务,每个标记有 @Scheduled
任务之间是串行执行的,使用的是同一个线程池,此线程池默认通过 Executors.newSingleThreadScheduledExecutor()
创建的单线程(核心线程=最大线程)的线程池。
解决方案
如何达到不同定时任务之间互不影响,都采用独立的定时任务线程池来执行呢?
由于 @Scheduled
的实现提供的扩展点比较单一,我们只能采用 API 编程的方式来完成了。
- 每个类中来创建定时任务线程池
- 调用需要执行的业务逻辑
@Component
@Slf4j
class ATask{// 创建线程池private ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();@PostConstructpublic void init(){scheduledExecutor.schedule(this::start, 10000, TimeUnit.MILLISECONDS);}public void start() {... // 原处理逻辑}
}