springboot深入学习——定时任务、异步任务以及它们所使用的线程池,单线程还是多线程?

前提:看之前我相信你已经掌握如何在springboot项目使用 @Scheduled 开启一个定时任务,以及使用 @Async 开启一个异步任务

 

说一些你可能不知道:

@Scheduled:同步阻塞任务(单线程)

1. @Scheduled定时任务是同步阻塞任务,因为它所使用的线程池是一个单线程的线程池,这意味着所有任务都是串行执行,只要前一个任务未执行完成,后面的任务都会一直等待下去,并且当一个任务未执行完成,它的下个触发周期会被忽略。
因为这些特点,当我们项目中的定时任务比较密集并且耗时比较长的时候需要特别注意,必要时需要我们提供自己配置的线程池

2.那么这个线程池是哪个呢?很多博客说是ThreadPoolTaskScheduler,并且说可以通过配置 spring.task.scheduling.pool.size 可以改变单线程为多线程,然而当你实际这样去做会发现并没有用,还是单线程同步阻塞任务。当我们点进去TaskSchedulingProperties和TaskSchedulingAutoConfiguration类进去看源码的时候我们会发现

也就是说实际上ThreadPoolTaskScheduler线程池并没有被实例化注册到spring容器,而是优先在TaskExecutionAutoConfiguration注册一个ThreadPoolTaskExecutor(继承自TaskScheduler)的线程池,这个线程池将在@Async的异步任务种被使用,下面会说。

如果我们再点进去看TaskExecutionAutoConfiguration会发现

而且ThreadPoolTaskScheduler与ThreadPoolTaskExecutor都是Executor的子类,随意可以得出结论,@Scheduled的线程池另有他池,并且在springboot的自动根配置中ThreadPoolTaskScheduler与ThreadPoolTaskExecutor只有ThreadPoolTaskExecutor被实例化注册到容器,并且互相干扰。也意味

3.既然不是ThreadPoolTaskScheduler那到底是哪一个?下面这些来搬自   Spring Boot 定时调度居然只用单线程  这篇博客里面也只有这部分是对的

实际上springboot走到了第五步,我们可以在控制台搜索“No TaskScheduler/ScheduledExecutorService bean found for scheduled processing” 这条打印信息,这部分的源码位于ScheduledAnnotationBeanPostProcessor.finishRegistration()中并且调用了ScheduledTaskRegistrar.afterPropertiesSet(); 这个方法又调用ScheduledTaskRegistrar.scheduleTasks()方法:
ScheduledAnnotationBeanPostProcessor在EnableScheduling中的SchedulingConfiguration被实例化注册到spring容器

在往下看

核心线程为 1,并且队列是无界(DelayedWorkQueue为无界队列)

豁然开朗,一切都明白过来,可以得出结论,在SpringBoot的自动化配置中,会给我们自动初始化一个 核心线程为 1,无界阻塞队列的ScheduledThreadPoolExecutor线程池,所以所有的定时任务都是同步阻塞串行运行的

 

 

@Async:异步非阻塞任务(多线程)

1. @Async任务为非阻塞任务,它的所有任务都会提交给springBoot自动给我们创建的一个名为applicationTaskExecutor(别名taskExecutor)类型为ThreadPoolTaskExecutor的线程池(你可以打控制台搜索到这个线程池初始化打印信息)里去执行,
并且当一个任务未执行完成,它的下个触发周期不会被忽略。

2. 通过配置文件我们可以对applicationTaskExecutor进行配置,包括核心线程数,线程名前缀等等,这些配置信息在TaskExecutionProperties bean初始化被注入,并在 TaskExecutionAutoConfiguration 实例化线程池被应用

可以看到配置起作用了

3.具体的流程  Spring Boot教程(21) – 默认线程池


源码:

 

 

说完springboot自动配置过程,接下来讲如何自定义配置修改这两个线程池

1.ThreadPoolTaskExecutor通过配置文件配置即可,ThreadPoolTaskExecutor默认是允许核心线程过期,可以设置 allow-core-thread-timeout: false  为不允许过期

 

2.修改@Scheduled任务的线程池,两种方式,两种都配置的话第一种的在@Scheduled会生效

第一种:通过实现SchedulingConfigurer接口的方式,要特别注意这种方式ThreadPoolTaskScheduler不能注册到spring容器成为一个spring bean,否则 ThreadPoolTaskExecutor(@Async)线程池将不会被初始化,如果你忘了为什么,请重新再看一次

@Configuration
public class ScheduleThreadPoolConfig implements SchedulingConfigurer {

    @Resource
    private TaskSchedulerBuilder builder;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = builder.build();
        taskScheduler.initialize();
        taskRegistrar.setScheduler(taskScheduler);
    }

}

第二种:手动注册   ThreadPoolTaskScheduler     和   ThreadPoolTaskExecutor 进入spring 容器

/**
 * @Description  定时任务与异步任务调度线程池初始化
 * @auther ccg
 * @create 2020-11-27 10:32
 */
@Configuration
public class InitThreadPoolTaskScheduler {

    @Bean(name = {"ThreadPoolTaskScheduler"})
    public ThreadPoolTaskScheduler ThreadPoolTaskScheduler(TaskSchedulerBuilder builder){
        ThreadPoolTaskScheduler taskScheduler = builder.build();
//        taskScheduler.initialize();
        return taskScheduler;
    }

    @Lazy
    @Bean(
            name = {"applicationTaskExecutor", "taskExecutor"}
    )
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        return builder.build();
    }
}

注意:如果你仔细观察你会发现,通过第一种方式需要调用线程池  initialize()  方法,而这个方法在ThreadPoolTaskScheduler和ThreadPoolTaskExecutor的共同父类  ExecutorConfigurationSupport 中被定义:

 

注意图中标记的这几个点,蓝色线部分是不是很熟悉(前面已经说过了),afterPropertiesSet()  是实现 InitializingBean 接口的方法,这个方法将在实例被注册到spring容器时被调用,可以用来初始化bean,而这些工作都是为了初始化真正的线程池ExecutorConfigurationSupport 下的 ExecutorService 成员变量,真正的实例化过程由 ExecutorConfigurationSupport  的抽象方法 initializeExecutor() ,这个方法将被其子类各自实现,ThreadPoolTaskExecutor 为例:

最终的线程池还是回到了jdk的  ThreadPoolExecutor,如此整个流程下来,是不是对springboot的自动化配置又了解了很多, 真棒!!!! 。

有帮助的记得点赞关注哦!!!

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值