Spring 异步注解 @Async 与线程池配置
使用@Async注解,在默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池,使用此线程池无法实现线程重用,每次调用都会新建一条线程。若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误
SimpleAsyncTaskExecutor 源码解析(省略一部分代码)
public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
implements AsyncListenableTaskExecutor, Serializable {
//ps: 这个参数值在 AsyncListenableTaskExecutor 的父类 AsyncTaskExecutor 里面
/** Constant that indicates no time limit. */
long TIMEOUT_INDEFINITE = Long.MAX_VALUE;
//ps: execute 方法只有两个,不带时间的方法的调用带时间的,并且这个时间是Long类型的最大值
/**
* Executes the given task, within a concurrency throttle
* if configured (through the superclass's settings).
* @see #doExecute(Runnable)
*/
@Override
public void execute(Runnable task) {
execute(task, TIMEOUT_INDEFINITE);
}
//ps: 调用这个方法,不管判断逻辑怎样,最终都会调用 doExecute 方法
/**
* Executes the given task, within a concurrency throttle
* if configured (through the superclass's settings).
* <p>Executes urgent tasks (with 'immediate' timeout) directly,
* bypassing the concurrency throttle (if active). All other
* tasks are subject to throttling.
* @see #TIMEOUT_IMMEDIATE
* @see #doExecute(Runnable)
*/
@Override
public void execute(Runnable task, long startTimeout) {
Assert.notNull(task, "Runnable must not be null");
Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
this.concurrencyThrottle.beforeAccess();
doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
}
else {
doExecute(taskToUse);
}
}
//ps: doExecute 方法只有一个,所有的最终都是回到这个方法, 最终调用 thread.start() 来启动这个线程,最后被分配时间片去执行run方法
//可以看到,不管怎样都是新建一个线程,只不过是新建线程的方式不一样, 如果线程工厂类不为空就使用工厂创建线程,否则使用父类的创建线程的方法来创建线程
/**
* Template method for the actual execution of a task.
* <p>The default implementation creates a new Thread and starts it.
* @param task the Runnable to execute
* @see #setThreadFactory
* @see #createThread
* @see java.lang.Thread#start()
*/
protected void doExecute(Runnable task) {
Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
thread.start();
}
//ps: 这个方法在当前类的父类 CustomizableThreadCreator 里面
//这个方法是新建一个线程
/**
* Template method for the creation of a new {@link Thread}.
* <p>The default implementation creates a new Thread for the given
* {@link Runnable}, applying an appropriate thread name.
* @param runnable the Runnable to execute
* @see #nextThreadName()
*/
public Thread createThread(Runnable runnable) {
Thread thread = new Thread(getThreadGroup(), runnable, nextThreadName());
thread.setPriority(getThreadPriority());
thread.setDaemon(isDaemon());
return thread;
}
}
再来看看 @Async 异步注解的部分实现
通过找到 @EnableAsync 看到,这个启用异步注解的文档里面明确写到, 异步注解使用的线程池是 org.springframework.core.task.SimpleAsyncTaskExecutor ,
还有一句 will be used to process async method invocations , 说的是这个线程池用于处理异步方法
异步处理的配置接口类 AsyncConfigurer
AsyncConfigurer 的实现类 AsyncConfigurerSupport
接下来自定义异步线程池
实现异步线程池配置接口类,把默认线程池配置改变为自定义的 ThreadPoolTaskExecutor
package com.biostime.material.purchase.config.log;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @Description Async 异步注解线程配置, AsyncConfigurer 默认配置实现,默认的线程池 SimpleAsyncTaskExecutor
* @author 21506 HuangXiang
* @since 2021/12/1
*/
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {
private static final Logger LOGGER = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
//线程池配置
@Bean(name = "asyncPoolTaskExecutor")
public ThreadPoolTaskExecutor executor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核销线程数
taskExecutor.setCorePoolSize(2);
//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(10);
//缓存队列
taskExecutor.setQueueCapacity(50);
//允许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(200);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("async-");
/**
* 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
* 通常有以下四种策略:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
* ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
//指定默认线程池
@Override
public Executor getAsyncExecutor() {
return executor();
}
//线程异常处理
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex , method , params) ->
LOGGER.error("线程池执行任务发送未知错误,执行方法:{}",method.getName(),ex);
}
}
接下来异步线程调用就会使用自定义的线程池!