「@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<TaskExecutorCustomizer> taskExecutorCustomizers;
        private final ObjectProvider<TaskDecorator> taskDecorator;
        public TaskExecutionAutoConfiguration(TaskExecutionProperties properties,
                                              ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
                                              ObjectProvider<TaskDecorator> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容