「@Async」SpringBoot中@Async默认的TaskExecutor到底是哪个?

写在前面的话

该文是在读到的众多有关@Async自定义线程池的博文中分析最为详细准确的。

本文为转载文章,版权所有归文章原始作者,如有需要请从原文转载:
原文链接:https://www.kuangstudy.com/bbs/1407940516238090242

秋招冲刺班 的视频中,飞哥讲到 [@Async](https://github.com/Async "@Async") 注解默认情况下用的是SimpleAsyncTaskExecutor 线程池,并且提到日志中的 taskId 是一直增长的。抱着探索的精神展开了如下测试:

问题复现

  • SpringBoot 版本 2.4.7
  • 测试机配置:双核Intel Core i5

异步方法如下:

    @Async
    public void sendMsg() {
        // todo :模拟耗时5秒
        try {
            Thread.sleep(5000);
            log.info("---------------发送消息--------");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // 添加积分,用异步进行处理和标记
    @Async
    public void addScore() {
        // todo :模拟耗时5秒
        try {
            Thread.sleep(5000);
            log.info("---------------处理积分--------");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

启动项目,多次调用这两个方法,得到如下日志结果。可以发现在当前环境下 task-${id} 这个 id 并不是一直增长的,而是一直在复用 1-8。这个时候可能就会有的小伙伴们会比较好奇,默认的不是 SimpleAsyncTaskExecutor 吗?为什么从日志打印的效果上看像是一直在复用 8 个线程,难道用的是 ThreadPoolTaskExecutor

11.jpg

验证猜想

为了验证我们的猜想,决定到源码里面去看一看,到底是用的是什么 TaskExecutor

首先找到了 @Async 的拦截器类org.springframework.aop.interceptor.AsyncExecutionInterceptor

    // 当执行 @Async 修饰的异步方法时,就会进入到这个方法中
    @Override
    @Nullable
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
        final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
        // 在这里得到了方法的执行器
        AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
        if (executor == null) {
            throw new IllegalStateException(
                    "No executor specified and no default executor set on AsyncExecutionInterceptor either");
        }
        // ...
        return doSubmit(task, executor, invocation.getMethod().getReturnType());
    }

    // AsyncExecutionInterceptor 父类中的方法,可以得到异步方法对应的执行器
    protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
        // executors 是一个缓存,只要执行过一次就会记录它要使用的 TaskExecutor,不需要每次执行都去寻找要使用的 `TaskExecutor`
        AsyncTaskExecutor executor = this.executors.get(method);
        if (executor == null) {
            Executor targetExecutor;
            // 如果通过 @Async("myExectuor") 指定了执行器 "myExectuor",就找指定的,如果没有就用默认的。
            String qualifier = getExecutorQualifier(method);
            if (StringUtils.hasLength(qualifier)) {
                targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
            } else {
                // 通过观察源码,可以发现 defaultExecutor 是通过 getDefaultExecutor 得到的
                targetExecutor = this.defaultExecutor.get();
            }
            if (targetExecutor == null) {
                return null;
            }
            executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
                    (AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
            this.executors.put(method, executor);
        }
        return executor;
    }

    // AsyncExecutionInterceptor 类中的 getDefaultExecutor
    // !!!!!!!!!!!!!重点!!!!!!!!!!!!!
    @Override
    @Nullable
    protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
        // 调用父类 super.getDefaultExecutor 得到 Executor
        Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
        // 如果没有得到默认的 Executor,则选用 SimpleAsyncTaskExecutor 
        return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
    }

    // 父类中的 getDefaultExecutor 
    // 会去 Spring 的容器中找有没有 TaskExecutor 或名称为 'taskExecutor' 为 Executor 的 
    @Nullable
    protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
        if (beanFactory != null) {
            try {
                return beanFactory.getBean(TaskExecutor.class);
            } catch (NoUniqueBeanDefinitionException ex) {
                return beanFactory.getBean("taskExecutor", Executor.class);
            } catch (NoSuchBeanDefinitionException ex) {
                return beanFactory.getBean("taskExecutor", Executor.class);
            }
        }
        return null;
    }

既然知道了在哪里记录了异步方法使用的什么 Executor,我们可以通过 DEBUG 模式去查看。

但是我们通过 DEBUG 查看 executors 缓存却发现在当前版本中的 SpringBoot @Async 默认是用的是居然是 ThreadPoolTaskExecutor

22.jpg

小结:在这里我简单总结一下,在 SpringFramework 中,如果没有自定义的 TaskExecutor 或名称为 ‘taskExecutor’ 的 Bean 对象,那么就会使用 SimpleAsyncTaskExecutor 。所以在当前版本 SpringFramework 中 @Async 默认是用的是 SimpleAsyncTaskExecutor

但是,从 DEBUG 模式中我们能发现,Executor defaultExecutor = super.getDefaultExecutor(beanFactory); 这里居然取到了 Executor,并且还是 ThreadPoolTaskExecutor,那么我们就可以想到是不是有谁帮我们创建了一个 ThreadPoolTaskExecutor 的 Bean 对象呢?

为什么容器中会有 ThreadPoolTaskExecutor 的 Bean 对象

通过上面的实现结果和问题,我联想到了 spring-boot-autoconfigure 这个包,因为这个包会自动帮我们生成一些需要的 Bean 对象。最终成功在 org.springframework.boot.autoconfigure.task 下找到了如下类:

    @ConditionalOnClass(ThreadPoolTaskExecutor.class)
    @Configuration
    @EnableConfigurationProperties(TaskExecutionProperties.class)
    public class TaskExecutionAutoConfiguration {
        public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
        private final TaskExecutionProperties properties;
        private final ObjectProvider taskExecutorCustomizers;
        private final ObjectProvider taskDecorator;
        public TaskExecutionAutoConfiguration(TaskExecutionProperties properties,
                                              ObjectProvider taskExecutorCustomizers,
                                              ObjectProvider taskDecorator) {
            this.properties = properties;
            this.taskExecutorCustomizers = taskExecutorCustomizers;
            this.taskDecorator = taskDecorator;
        }
        @Bean
        @ConditionalOnMissingBean
        public TaskExecutorBuilder taskExecutorBuilder() {
            TaskExecutionProperties.Pool pool = this.properties.getPool();
            TaskExecutorBuilder builder = new TaskExecutorBuilder();
            builder = builder.queueCapacity(pool.getQueueCapacity());
            builder = builder.corePoolSize(pool.getCoreSize());
            builder = builder.maxPoolSize(pool.getMaxSize());
            builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
            builder = builder.keepAlive(pool.getKeepAlive());
            builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
            builder = builder.customizers(this.taskExecutorCustomizers);
            builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
            return builder;
        }
        // 只要没有 Executor 的 Bean 对象,那么就会帮你生成一个 ThreadPoolTaskExecutor 的 Bean 对象
        @Lazy
        @Bean(name = APPLICATION_TASK_EXECUTOR_BEAN_NAME)
        @ConditionalOnMissingBean(Executor.class)
        public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
            return builder.build();
        }
    }

小结:由于 spring-boot-autoconfigure 是 SpringBoot 一个重要的依赖,所以只要是 SpringBoot 项目就一定会依赖它,可以断定 ThreadPoolTaskExecutor 是 SpringBoot 项目中 Executor 的默认 Bean 对象。而 [@Async](https://github.com/Async "@Async") 在选择执行器的时候会先去 IOC 容器中先找是否有 TaskExecutor 的 Bean对象,所以在当前版本 SpringBoot 中,@Async 的默认 TaskExecutor 是 ThreadPoolTaskExecutor

验证飞哥的说法

既然飞哥说默认的 SimpleAsyncTaskExecutor,我选择相信飞哥,因为可能以前的版本的 SpringBoot 并不会默认帮我们创建一个 ThreadPoolTaskExecutor 的 Bean 对象。

查看源码,我找到了一个关键的信息。这说明 TaskExecutionAutoConfiguration 是从 2.1.0 版本才开始有的。

33.jpg

那么我将版本换到 2.1.0 之前再试试。在这里我选用了 2.1.0 的上一个版本 2.0.9 (完整的应该是 2.0.9.RELEASE)。

通过查看 spring-boot-autoconfigure-2.0.9 包,并没有找到 TaskExecutionAutoConfiguration 这个类。但是还需要通过代码的执行效果来验证。

验证 task-id 是不是一直增加

44.jpg

验证是由于从 Spring 容器中拿不到,然后默认使用 SimpleAsyncTaskExecutor

55.jpg

注:验证时,一定要是第一次调用异步方法,要不然就会走缓存。

验证缓存中存是的 SimpleAsyncTaskExecutor

66.jpg

完善飞哥的说法

总结:在 SpringBoot 2.0.9 版本及以前,@Async 默认使用的是 SimpleAsyncTaskExecutor;从 2.1.0 开始到当前最新的 2.5.1,@Async 默认使用的是 ThreadPoolTaskExecutor

版权声明

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明,KuangStudy,以学为伴,一生相伴!
原文链接:https://www.kuangstudy.com/bbs/1407940516238090242

你可能感兴趣的:(「@Async」SpringBoot中@Async默认的TaskExecutor到底是哪个?)