【老王最佳实践-5】spring 多线程池的配置及使用&利用FactoryBean做参数配置化技巧

在 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() {
    ......
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老王学源码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值