在 Spring 中使用异步线程池时非常方便的,只需要在被执行的方法上加上 @Async
就行。
开启 Spring 异步需要添加配置 @EnableAsync
但是,有时我们可能需要多个异步线程池,最好每个线程池都是可配置的。
多个线程池给不同的业务场景来使用,防止线程池卡死,或者任务堆积影响到其他业务。
Spring 中配置多线程池
/**
* 定义不同类型的 Executor,供异步任务执行时进行选择
*/
@Slf4j
@Configuration
@EnableAsync(proxyTargetClass = true)
public class ExecutorsConfig {
public static final String BIZ_EXECUTOR = "bizExecutor";
@Autowired
private AsyncConfiguration asyncConfiguration;
/** 默认的异步执行器 */
@Primary
@Bean
public Executor defaultExecutor() {
ExecutorProperties props = asyncConfiguration.normalExecutorProperties();
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(props.getCorePoolSize());
executor.setMaxPoolSize(props.getMaxPoolSize());
executor.setKeepAliveSeconds(props.getKeepAliveSeconds());
executor.setQueueCapacity(props.getQueueCapacity());
executor.setAllowCoreThreadTimeOut(props.getAllowCoreThreadTimeOut());
executor.setThreadNamePrefix(props.getThreadNamePrefix());
log.info("use normalExecutorProperties:{}", props);
executor.setTaskDecorator(new MyThreadDecorator());
executor.initialize();
return executor;
}
@Bean(BIZ_EXECUTOR)
public Executor bizExecutor() {
ExecutorProperties props = asyncConfiguration.balanceBizWarnExecutorProperties();
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(props.getCorePoolSize());
executor.setMaxPoolSize(props.getMaxPoolSize());
executor.setKeepAliveSeconds(props.getKeepAliveSeconds());
executor.setQueueCapacity(props.getQueueCapacity());
executor.setAllowCoreThreadTimeOut(props.getAllowCoreThreadTimeOut());
executor.setThreadNamePrefix(props.getThreadNamePrefix());
log.info("use balanceBizWarnExecutorProperties:{}", props);
executor.setTaskDecorator(new MyThreadDecorator());
executor.setRejectedExecutionHandler(new DiscardOldest());
executor.initialize();
return executor;
}
}
线程池参数配置化:
@Slf4j
@Configuration
public class AsyncConfiguration {
@ConfigurationProperties(prefix = "executor.biz")
@Bean
public ExecutorProperties bizExecutorProperties() {
ExecutorProperties prop = new ExecutorProperties();
prop.setCorePoolSize(11);
prop.setMaxPoolSize(20);
prop.setQueueCapacity(200);
prop.setThreadNamePrefix("async-balanceBizWarn-");
log.info("balanceBizWarnExecutorProperties:{}", prop);
return prop;
}
@ConfigurationProperties(prefix = "executor.normal")
@Bean
public ExecutorProperties normalExecutorProperties() {
ExecutorProperties prop = new ExecutorProperties();
// 普通的异步任务固定开 5 个线程
prop.setCorePoolSize(5);
log.info("normalExecutorProperties:{}", prop);
return prop;
}
/**
* ExecutorProperties
* @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
*/
@Data
public static class ExecutorProperties {
/** 核心线程池大小 */
private Integer corePoolSize = 1;
/** 最大线程池大小*/
private Integer maxPoolSize = Integer.MAX_VALUE;
/** 线程池队列容量(默认Integer.MAX_VALUE) */
private Integer queueCapacity = Integer.MAX_VALUE;
/** 线程池空闲时长(秒) */
private Integer keepAliveSeconds = 60;
private Boolean allowCoreThreadTimeOut = false;
/** 线程池名称 */
private String threadNamePrefix = "async-normal-";
}
}
进化版:使用 FactoryBean 的方式将线程池参数做成配置化
单独定义配置类,显得有点繁琐,我们可以使用 spring FactoryBean 来定义 bean。
配置参数可以放在 FactoryBean 中,spring 生成 bean 的时候会调用 getObject()
方法,这样就可以使用到我们配置的参数了。
@Slf4j
@Data
public class ExecutorFactoryBean implements FactoryBean<ThreadPoolTaskExecutor> {
private int corePoolSize = 1;
private int maxPoolSize = Integer.MAX_VALUE;
private int keepAliveSeconds = 60;
private boolean allowCoreThreadTimeOut = false;
private int queueCapacity = Integer.MAX_VALUE;
private String threadNamePrefix;
@Override
public ThreadPoolTaskExecutor getObject() throws Exception {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setQueueCapacity(queueCapacity);
executor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);
executor.setThreadNamePrefix(threadNamePrefix);
log.info("use ExecutorProperties:{}", this);
executor.setRejectedExecutionHandler(new DiscardOldest());
executor.initialize();
return executor;
}
@Override
public Class<?> getObjectType() {
return ExecutorService.class;
}
}
通过 FactoryBean 来配置线程池:
@Slf4j
@Configuration
@EnableAsync(proxyTargetClass = true)
public class ExecutorsConfig {
public static final String BIZ_EXECUTOR = "bizExecutor";
/** 默认的异步执行器*/
@Primary
@ConfigurationProperties(prefix = "executor.normal")
@Bean
public ExecutorFactoryBean defaultExecutor() {
return new ExecutorFactoryBean();
}
@Bean(BIZ_EXECUTOR)
@ConfigurationProperties(prefix = "executor.biz")
public ExecutorFactoryBean bizExecutor() {
return new ExecutorFactoryBean();
}
}
【最佳实践】:使用 ThreadPoolExecutorFactoryBean 来配置多线程池
/**
* 定义不同类型的 Executor,供异步任务执行时进行选择
*/
@Slf4j
@Configuration
@EnableAsync(proxyTargetClass = true)
public class ExecutorsConfig2 {
public static final String BIZ_EXECUTOR = "bizExecutor";
/** 默认的异步执行器 */
@Primary
@ConfigurationProperties(prefix = "executor.normal")
@Bean
public ThreadPoolExecutorFactoryBean defaultExecutor() {
return new ThreadPoolExecutorFactoryBean();
}
@Bean(BIZ_EXECUTOR)
@ConfigurationProperties(prefix = "executor.biz")
public ThreadPoolExecutorFactoryBean bizExecutor() {
ThreadPoolExecutorFactoryBean executorFb = new ThreadPoolExecutorFactoryBean();
executorFb.setRejectedExecutionHandler(new DiscardOldest());
return executorFb;
}
}
举一反三:
如果我们需要将 bean 的一些参数做成配置化,也可以使用 FactoryBean 的方式,将需要配置的参数抽出放在 FactoryBean 中,在 getObject()
时,使用这些参数来创建 bean
业务代码指定线程池使用
业务代码指定线程池的方法:
// 在注解中指定线程池 bean 的名称。如果不指定就使用的默认线程池
@Async(BIZ_EXECUTOR)
public void asyncCall() {
......
}