applicationTaskExecutor Spring 内置线程池失效

概述

项目中在自定义了两个线程池后有一个线程池注入的地方报错了

java 复制代码
public A(Executor executor) {
        super(executor);
    }

这是一个类注入线程池的方法,名字这里用A替代,一开始项目中并没有使用自定义线程池,有并发需求很多地方都是使用的公共线程池ForkJoinPool, 所以一直没遇到问题,但是我的需求中涉及到集合中的多个元素都要远程调用,于是自定义了线程池:

java 复制代码
@Configuration
@EnableConfigurationProperties({TaskExecutionProperties.class})
public class ThreadPoolConfig {

    // CPU核心数(用于计算默认线程数)
    private static final int CPU_CORES = Runtime.getRuntime().availableProcessors();

    @Bean(name = "r1")
    public Executor r1() {
        // IO密集型
        ThreadPoolTaskExecutor remoteCallExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数
        remoteCallExecutor.setCorePoolSize(Math.min(CPU_CORES, 8));
        // 最大线程数
        remoteCallExecutor.setMaxPoolSize(Math.min(CPU_CORES * 2, 20));
        // 队列长度
        remoteCallExecutor.setQueueCapacity(300);
        // 线程空闲时间
        remoteCallExecutor.setKeepAliveSeconds(60);
        // 线程名字前缀
        remoteCallExecutor.setThreadNamePrefix("remoteCallExecutor-");
        // 线程拒绝策略
        remoteCallExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待任务的超时时间
        remoteCallExecutor.setAwaitTerminationSeconds(60);
        // 线程初始化
        remoteCallExecutor.initialize();

        return remoteCallExecutor;
    }


@Bean(name = "r2")
    public Executor r2() {
        // IO密集型
        ThreadPoolTaskExecutor remoteCallExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数
        remoteCallExecutor.setCorePoolSize(Math.min(CPU_CORES, 8));
        // 最大线程数
        remoteCallExecutor.setMaxPoolSize(Math.min(CPU_CORES * 2, 20));
        // 队列长度
        remoteCallExecutor.setQueueCapacity(300);
        // 线程空闲时间
        remoteCallExecutor.setKeepAliveSeconds(60);
        // 线程名字前缀
        remoteCallExecutor.setThreadNamePrefix("remoteCallExecutor-");
        // 线程拒绝策略
        remoteCallExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待任务的超时时间
        remoteCallExecutor.setAwaitTerminationSeconds(60);
        // 线程初始化
        remoteCallExecutor.initialize();

        return remoteCallExecutor;
    }

    

}

这时项目启动,报错:

***************************

APPLICATION FAILED TO START

***************************

Description:

Parameter 0 of constructor in A required a single bean, but 2 were found:

  • r1: defined by method 'r1' in class path resource xxx

  • r2: defined by method 'r2' in class path resource xxx

于是,先去掉了自定义线程池,debug发现没定义线程池之前注入的是一个名为:applicationTaskExecutor 的线程池,于是我加了 @Qualifier("applicationTaskExecutor")之后又打开了自定义线程池,项目启动还是报错

java 复制代码
public A(@Qualifier("applicationTaskExecutor") Executor executor) {
        super(executor);
    }
复制代码
结果还是报错说找不到这个applicationTaskExecutor Bean,很奇怪,于是我创建了一个@PostConstract()标记的方法,打印了所有Executor 类的Bean的名字,发现没有名为 applicationTaskExecutor 的Bean
java 复制代码
 Map<String, Executor> executorMap = applicationContext.getBeansOfType(Executor.class);

原理

于是查找了资料看到了applicationTaskExecutor这个Bean创建的类

java 复制代码
@ConditionalOnClass({ThreadPoolTaskExecutor.class})
@Configuration(
    proxyBeanMethods = false
)
@EnableConfigurationProperties({TaskExecutionProperties.class})
public class TaskExecutionAutoConfiguration {
    public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";

    @Bean
    @ConditionalOnMissingBean
    public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties, ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers, ObjectProvider<TaskDecorator> taskDecorator) {
        TaskExecutionProperties.Pool pool = 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());
        TaskExecutionProperties.Shutdown shutdown = properties.getShutdown();
        builder = builder.awaitTermination(shutdown.isAwaitTermination());
        builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
        builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
        Stream var10001 = taskExecutorCustomizers.orderedStream();
        var10001.getClass();
        builder = builder.customizers(var10001::iterator);
        builder = builder.taskDecorator((TaskDecorator)taskDecorator.getIfUnique());
        return builder;
    }

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

可以看到创建的方法上面标记了 @ConditionalOnMissingBean({Executor.class}) 说明当自定义了线程池之后spring就不会帮助创建内置的线程池了,这也是一个很容易踩坑的地方,一开始我其实只定义了一个线程池,这时候没有报错,A类注入的时候注入的是我自定义的线程池,当时还不知道,当我定义第二个线程池的时候才发现,定义的参数都不同,很可能会出现一些问题。

解决方案

可以自己创建一个与内置线程相同的线程池,命名为applicationTaskExecutor供需要使用的地方使用或者根据需求业务定义合适的线程池来使用

java 复制代码
@Bean("applicationTaskExecutor")
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        return builder.build();
    }

    @Bean
    public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties, ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers, ObjectProvider<TaskDecorator> taskDecorator) {
        TaskExecutionProperties.Pool pool = 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());
        TaskExecutionProperties.Shutdown shutdown = properties.getShutdown();
        builder = builder.awaitTermination(shutdown.isAwaitTermination());
        builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
        builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
        Stream<TaskExecutorCustomizer> var10001 = taskExecutorCustomizers.orderedStream();
        var10001.getClass();
        builder = builder.customizers(var10001::iterator);
        builder = builder.taskDecorator((TaskDecorator) taskDecorator.getIfUnique());
        return builder;
    }
相关推荐
Lhappy嘻嘻5 小时前
Java IO|File 文件操作 + 字节流 / 字符流完整笔记 + 递归删除文件实战
java·笔记·php
伊玛目的门徒5 小时前
试用leetcode之典中典 二数之和问题
java·算法·leetcode
AI-好学者7 小时前
阶段一-图数据库基础与PropertyGraph模型
数据库·rag·knowledge graph·graphrag
其实防守也摸鱼8 小时前
运维--学习阶段问题解答(1)(自测)
linux·运维·服务器·数据库·学习·自动化·命令模式
懒鸟一枚8 小时前
深入理解 Linux 内存、Swap 交换分区与分页机制的关系
java·linux·数据库
龙仔7258 小时前
SQL Server 创建只读账号完整操作(分两种场景:SSMS图形界面 + T-SQL脚本)
数据库·sql·oracle
霁月的小屋9 小时前
生产环境中的事务实践——银行系统上线记(四)
数据库
Database_Cool_9 小时前
AI 应用数据底座首选:阿里云 PolarDB 为大模型 RAG 提供一体化支撑
数据库·阿里云
玖玥拾9 小时前
C# 语言进阶(十五)C# 游戏服务端 MySQL 数据库
服务器·开发语言·网络·数据库·mysql·c#
我命由我1234510 小时前
执行 Gradle 指令报错,无法将“grep”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
android·java·java-ee·android studio·android jetpack·android-studio·android runtime