Spring @Scheduled单线程单实例的坑

文章目录

    • 前言
    • 背景
    • 验证
    • 解决方案

前言

在 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();

Spring @Scheduled单线程单实例的坑_第1张图片

观察 thread 名称,分别在每个 @Scheduled 方法中都打断点

第一个 FaceTask#start() 方法,threadName = pool-8-thread-1

Spring @Scheduled单线程单实例的坑_第2张图片

第二个 ProfileTask#start() 方法,threadName = pool-8-thread-1

Spring @Scheduled单线程单实例的坑_第3张图片

第三个 TmpFaceTask#faceDelayPush() 方法,threadName = pool-8-thread-1

Spring @Scheduled单线程单实例的坑_第4张图片

通过源码+代码调试可以得到结论,在同一个 Spring 容器中(项目中),使用多个 @Scheduled 定时任务,每个标记有 @Scheduled 任务之间是串行执行的,使用的是同一个线程池,此线程池默认通过 Executors.newSingleThreadScheduledExecutor() 创建的单线程(核心线程=最大线程)的线程池。

解决方案

如何达到不同定时任务之间互不影响,都采用独立的定时任务线程池来执行呢?

由于 @Scheduled 的实现提供的扩展点比较单一,我们只能采用 API 编程的方式来完成了。

  • 每个类中来创建定时任务线程池
  • 调用需要执行的业务逻辑
@Component
@Slf4j
class ATask{
    // 创建线程池
    private ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();

    @PostConstruct
    public void init(){
        scheduledExecutor.schedule(this::start, 10000, TimeUnit.MILLISECONDS);
    }

    public void start() {
    ... // 原处理逻辑
    }
}

你可能感兴趣的:(java,Scheduled,spring)