为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

日常工作中,我们时常会使用 @Async 注解将一个方法或一整个类里面的所有方法标注为异步执行方法。

一般使用时都会明确指定每个异步任务对应使用的是个线程池。一般不建议只使用一个线程池或者多种类型的任务混用一个线程池,应该将线程池分类。

一堆异步任务使用同一个线程池有明显的弊端,如果有一个非重要异步任务耗时太久导致线程池耗尽甚至因为线程池配置不合理而导致了OOM的发生,那么势必会影响到耗时比较短但是又非常重要的任务,所以一般线程池都应该按照使用类型分类并创建多个。

我们可以通过 value 属性来指定执行这个异步任务的线程池
org.springframework.scheduling.annotation.Async

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

如果使用时没有明确指定线程池,会发生什么?

1.  如果项目中没有配置过线程池,即 classpath下没有 Executer 时,默认新构建一个 ThreadPoolTaskExecutor 线程池。
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

org.springframework.boot.task.TaskExecutorBuilder

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

2.  如果已配且只配置了一个线程池的时候,默认使用用户自己配置的线程池。
3.  如果用户配置了多个线程池,那么会使用 SimpleAsyncTaskExecutor 作为默认线程池。

前文已经交代过所有异步任务放在一起不合适,而且第一种和第三种的默认实现还有更加严重的问题。



为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

# PART.01


ThreadPoolTaskExecutor

先来看 ThreadPoolTaskExecutor

SpringBoot 默认使用 TaskExecutionProperties 类来配置构建 ThreadPoolTaskExecutor线程池。

org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

TaskExecutionProperties默认配置如下:
org.springframework.boot.autoconfigure.task.TaskExecutionProperties

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

我们可以看到 队列容量 和 最大线程数 都是 Integer.MAX_VALUE,这非常可怕。

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

想想这样会发生什么?一旦8个核心线程长时间被占用,那么队列就会一直不断的增长,然后OOM

我们可以对默认线程池做一些基础参数配置来避免上述问题的发生,配置以 spring.task.execution开头。

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

spring.task.execution.pool.core-size: 核心线程数
spring.task.execution.pool.max-size: 最大线程数
spring.task.execution.pool.queue-capacity: 队列容量
spring.task.execution.pool.keep-alive: 线程终止前允许保持的空闲时间
spring.task.execution.pool.allow-core-thread-timeout: 是否允许核心线程超时
spring.task.execution.thread-name-prefix: 线程名称前缀


为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

# PART.02


SimpleAsyncTaskExecutor

SimpleAsyncTaskExecutor 默认每次执行异步任务的时候都会创建一个新的线程,问题同样严重。

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”想想看,如果系统源源不断的一直创建异步任务,从而导致一个个的线程被源源不断的创建出来,然后肯定会OOM了。

针对这个问题,SimpleAsyncTaskExecutor提供了个限流机制,可以通过 concurrencyLimit 来限制最多同时有多少个线程在执行,超过限制就等待,直到前面执行的线程结束后才能创建新的线程来执行等待任务。
虽然可以控制线程数,不会发生OOM了,但会出现主线程等待,线程竞争的情况。
而且SimpleAsyncTaskExecutor并不能达到线程复用的目的,所以严格意义上来说 SimpleAsyncTaskExecutor 并不是线程池,正如它的名称一样也确实没有包括pool这个关键词汇。

为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”


为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

# PART.03


创建自己的线程池

以下为创建线程池的代码示例,可以根据需求创建多个,并在使用 @Async 的时候显示指定。

@Bean
public Executor asyncExecutorService() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(8);
    executor.setMaxPoolSize(16);
    executor.setQueueCapacity(1000);
    executor.setKeepAliveSeconds(300);
    executor.setThreadNamePrefix("My-Executor-");
    // 执行装饰器
    executor.setTaskDecorator((runnable) -> {
       // 获取上级线程中的一些信息,一般存放在ThreadLocal中。
        long tid = Thread.currentThread().getId();
        return () -> {
            try {
                if (Thread.currentThread().getId() != tid) {
                    // 将上级线程中获取的信息存到子线程中
                }
                runnable.run();
            } finally {
                if (Thread.currentThread().getId() != tid) {
                    // 清空子线程ThreadLocal
                }
            }
        };
    });
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
做的更高级一点的话,可以使用 动态线程池,通过配置中心来随时动态调整线程池参数配置。
详见 -> https://gitee.com/dromara/dynamic-tp


为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

点个在看你最好看

原文始发于微信公众号(i余数):为什么建议 “在使用SpringBoot的@Async注解时一定要指定线程池?”

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/194112.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!