SpringBoot中@Async的实现方式探索

科技公元 后端 2024-08-01

SpringBoot中@Async的实现方式探索

背景

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

代码解读
复制代码
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,我们看看变更之后是怎么实现默认线程池的kotlin

代码解读
复制代码
@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; } kotlin
代码解读
复制代码
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

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

SpringBoot中@Async的实现方式探索

结语

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

转载来源:https://juejin.cn/post/7370963861644771337

Apipost 私有化火热进行中

评论