文章目录
- 一、需求场景
- 二、需要的核心注解
- 三、自定义线程池配置
- 3.1 所需依赖
- 3.2 定义基类 AsyncConstants
- 3.3 定义各线程池配置类
- PrimaryAsyncConstants
- SecondaryAsyncConstants
- 3.4 定义配置文件
- 四、多线程池实现
- 4.1 生成 Executor 对象
- 4.2 声明异步方法
- 4.3 调用示例
一、需求场景
通常情况,在小项目中,业务单一,单个线程池能很好地满足业务需求,但如果业务种类多呢?单个线程池还能满足么?
从隔离性的角度出发,我们一般希望一块业务由独立的线程池负责处理。因此,对于多业务项目,最合适的选择是引入多线程池。
在 SpringBoot 中,对于单线程池已经实现了很好的集成,但在多线程池上可以参考的资料比较少。对此,本文提出来一种基于 SpringBoot 实现多线程池的方法,希望能对诸君有所帮助。
二、需要的核心注解
在 SpringBoot 中实现线程池,需要以下两个核心注解:
- @EnableAsync:通过该注解,开启对异步任务的支持
- @Async:声明当前方法为异步方法
三、自定义线程池配置
在集成线程池之前,我们首先需要实现线程池的自定义配置,保证在项目中各线程池的可配置性。
3.1 所需依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
注:该依赖的作用是实现配置文件到实体类的字段定位,主要用于协助开发人员。没有添加该依赖对程序的运行结果不会产生影响。
3.2 定义基类 AsyncConstants
public class AsyncConstants {
/**
* 核心线程数
*/
private Integer corePoolSize = 8;
/**
* 最大线程数
*/
private Integer maxPoolSize = 16;
/**
* 空闲线程存活时间
*/
private Integer keepAliveSeconds = 60;
/**
* 等待队列长度
*/
private Integer queueCapacity = 100;
public Integer getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(Integer corePoolSize) {
this.corePoolSize = corePoolSize;
}
public Integer getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(Integer maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public Integer getKeepAliveSeconds() {
return keepAliveSeconds;
}
public void setKeepAliveSeconds(Integer keepAliveSeconds) {
this.keepAliveSeconds = keepAliveSeconds;
}
public Integer getQueueCapacity() {
return queueCapacity;
}
public void setQueueCapacity(Integer queueCapacity) {
this.queueCapacity = queueCapacity;
}
}
3.3 定义各线程池配置类
PrimaryAsyncConstants
@Component
@ConfigurationProperties(prefix = "primary.async")
public class PrimaryAsyncConstants extends AsyncConstants {
}
SecondaryAsyncConstants
@Component
@ConfigurationProperties(prefix = "secondary.async")
public class SecondaryAsyncConstants extends AsyncConstants {
}
注解作用如下:
- @Component:声明该类为 Spring 组件,交由容器管理
- @ConfigurationProperties:声明该实体类对应的配置字段前缀
3.4 定义配置文件
# 线程池配置
primary.async.corePoolSize=20
primary.async.maxPoolSize=40
primary.async.keepAliveSeconds=120
primary.async.queueCapacity=100
secondary.async.corePoolSize=10
secondary.async.maxPoolSize=20
secondary.async.keepAliveSeconds=120
secondary.async.queueCapacity=100
四、多线程池实现
4.1 生成 Executor 对象
上面,我们已经实现了线程池的自定义配置。现在,我们需要将这些配置信息注入到线程池对象 ThreadPoolTaskExecutor
中,生成可用的 Executor 对象。
@Configuration
@EnableAsync
public class AsyncConfig {
private PrimaryAsyncConstants primaryAsyncConstants;
private SecondaryAsyncConstants secondaryAsyncConstants;
@Autowired
public AsyncConfig(PrimaryAsyncConstants primaryAsyncConstants, SecondaryAsyncConstants secondaryAsyncConstants) {
this.primaryAsyncConstants = primaryAsyncConstants;
this.secondaryAsyncConstants = secondaryAsyncConstants;
}
@Bean(name = "primaryExecutor")
public Executor getPrimaryExecutor() {
return initExecutor(primaryAsyncConstants, "PrimaryExecutor-");
}
@Bean(name = "secondaryExecutor")
public Executor getSecondaryExecutor() {
return initExecutor(secondaryAsyncConstants, "SecondaryExecutor-");
}
private ThreadPoolTaskExecutor initExecutor(AsyncConstants constants, String prefix) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(constants.getCorePoolSize());
executor.setMaxPoolSize(constants.getMaxPoolSize());
executor.setKeepAliveSeconds(constants.getKeepAliveSeconds());
executor.setQueueCapacity(constants.getQueueCapacity());
executor.setThreadNamePrefix(prefix);
return executor;
}
}
上述代码中,getPrimaryExecutor()
与 getSecondaryExecutor()
方法分别实例化了对应的 Executor 对象,并通过注解 @Bean
将其纳入容器中。
需要注意的是,@Bean
的名称很重要,之后我们决定使用哪个线程池与该名称相关。
4.2 声明异步方法
我们编写一个 Service
实现类,并通过 @Async
注解声明其方法为异步方法。@Async
中的值要对应上述生成的 Executor 对象的名称。例如,本例中,@Async(value = "primaryExecutor")
表明该异步方法由名为 primaryExecutor
的线程池执行。
@Slf4j
@Service
public class PrimaryServiceImpl implements PrimaryService {
@Override
@Async(value = "primaryExecutor")
public void test() {
log.info(Thread.currentThread().getName());
}
}
4.3 调用示例
编写以下调用代码:
@SpringBootApplication
public class MyApplication implements CommandLineRunner {
private PrimaryService primaryService;
@Autowired
public MyApplication(PrimaryService primaryService) {
this.primaryService = primaryService;
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
@Override
public void run(String... args) {
for (int i = 0; i < 3; i++) {
primaryService.test();
}
}
}
可以在控制台看到以下输出信息,证明线程池调用成功:
PrimaryExecutor-1
PrimaryExecutor-3
PrimaryExecutor-2