SpringBoot中@Async的实现方式探索

45 阅读2分钟

背景

最近看代码时看到小伙伴提交了这样一段代码

public class AsyncExecutorConfig extends AsyncConfigurerSupport {
    @Override
    public Executor getAsyncExecutor() {
        return new ThreadPoolExecutor(
                10,
                300,
                30,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(2000),
                ThreadFactoryBuilder.create().setNamePrefix("bid-async-").build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

代码的目的是重新实现@Aysnc的默认线程处理,于是有一个疑问,为什么要进行重写,使用默认实现不可以吗?@Async默认的实现方式是什么?原理又是什么呢?

带着这个疑问进行了相关探索

探索

从修改默认实现上看,默认实现一定也是一个线程池,于是查看他的默认线程池是什么?首先我看到的默认实现是会使用SimpleAsyncTaskExecutor线程池,那看SimpleAsyncTaskExecutor线程池的实现方式,他的方式是有一个任务就去创建一个线程,而且创建的线程不会复用且不会销毁,当任务过多时,会出现cpu过高的情况,基于此,原有的实现是一定存在问题的。

随着探索的加深,发现存在另一个答案,就是他的实现也是ThreadPoolExecutor,在springboot2.1.0之前默认是实现是SimpleAsyncTaskExecutor,2.1.0之后默认实现变更为ThreadPoolExecutor,我们看看变更之后是怎么实现默认线程池的

@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {

	/**
	 * Bean name of the application {@link TaskExecutor}.
	 */
	public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";

	private final TaskExecutionProperties properties;

	private final ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers;

	private final ObjectProvider<TaskDecorator> taskDecorator;

	public TaskExecutionAutoConfiguration(TaskExecutionProperties properties,
			ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
			ObjectProvider<TaskDecorator> taskDecorator) {
		this.properties = properties;
		this.taskExecutorCustomizers = taskExecutorCustomizers;
		this.taskDecorator = taskDecorator;
	}
        
        
        
protected ExecutorService initializeExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
        BlockingQueue<Runnable> queue = this.createQueue(this.queueCapacity);
        ThreadPoolExecutor executor;
        if (this.taskDecorator != null) {
            executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, (long)this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler) {
                public void execute(Runnable command) {
                    Runnable decorated = ThreadPoolTaskExecutor.this.taskDecorator.decorate(command);
                    if (decorated != command) {
                        ThreadPoolTaskExecutor.this.decoratedTaskMap.put(decorated, command);
                    }

                    super.execute(decorated);
                }
            };
        } else {
            executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, (long)this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler);
        }

默认线程池的定义了核心线程数,最大线程数,队列以及线程存活时间,他们的参数分别为

  • 核心线程数:8
  • 最大线程数:int的最大值
  • 队列:阻塞队列且最大值为int的最大值
  • 线程存活时间:60s
  • 拒绝策略:AbortPolicy

对比上一个默认实现,一个很大的进步是线程是可以重复利用的,这样就大大减少了资源的使用,但是还是存在一个较大的风险,就是队列长度过长,当任务过多时会将大量待执行的任务放到队列里面,导致程序处理不过来,最大线程数其实没有利用起来,所以保险起见,还是要自己手动实现一个线程池,防止出现相关问题

image.png

结语

线程池可以很好的提高功能的效率,但是也要考虑使用过程中出现的问题,比如多线程写,又比如使用不当造成的系统性能问题,基于此,是很有必要了解他们的执行方式和原理