相信大家经常看到这样的文章:
当使用@Async不指定线程池时,Spring会默认使用SimpleAsyncTaskExecutor线程池,默认情况下,SimpleAsyncTaskExecutor不会限制线程创建的个数,这会导致资源耗尽。这个线程池和我们印象中的的线程池可以说是相悖的。如果需要使用SimpleAsyncTaskExecutor,则需指定线程上限(调用setConcurrencyLimit方法),避免在极端情况下出现资源耗尽的问题。另外,该任务执行器并没有执行拒绝策略,这也是在线上环境需谨慎使用的原因之一。
所以大部分人使用spring的异步都需要写如下的代码:
@Configuration
@EnableAsync
public class BaseAsyncConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程池数量,方法: 返回可用处理器的Java虚拟机的数量。
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
//最大线程数量
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors()*5);
//线程池的队列容量
executor.setQueueCapacity(Runtime.getRuntime().availableProcessors()*2);
//线程名称的前缀
executor.setThreadNamePrefix("this-excutor-");
// setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
// CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
//executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
/*异步任务中异常处理*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (Throwable ex, Method method, Object... params)->{
//todo 异步方法异常处理
System.out.println("class#method: " + method.getDeclaringClass().getName() + "#" + method.getName());
System.out.println("type : " + ex.getClass().getName());
System.out.println("exception : " + ex.getMessage());
};
}
}
但是我很多时候并没有写如上代码,只添加了@Async直接使用(当然前提是要添加@EnableAsync注解),但是并没有发现spring每次执行的时候都创建了新的线程,而是用了线程池。于是乎怀着好奇的心情,写了如下代码,并执行多次:
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class HelloServiceImpl implements HelloService {
@Override
@Async
public void hello() {
log.info("当前线程:{}", Thread.currentThread().getName());
}
}
执行结果如下:
2023-07-14 13:51:43.098 INFO 12268 --- [ task-1] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-1
2023-07-14 13:51:43.257 INFO 12268 --- [ task-2] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-2
2023-07-14 13:51:43.405 INFO 12268 --- [ task-3] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-3
2023-07-14 13:51:43.570 INFO 12268 --- [ task-4] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-4
2023-07-14 13:51:43.756 INFO 12268 --- [ task-5] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-5
2023-07-14 13:51:43.944 INFO 12268 --- [ task-6] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-6
2023-07-14 13:51:44.227 INFO 12268 --- [ task-7] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-7
2023-07-14 13:51:44.414 INFO 12268 --- [ task-8] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-8
2023-07-14 13:51:44.654 INFO 12268 --- [ task-1] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-1
2023-07-14 13:51:44.851 INFO 12268 --- [ task-2] c.yapler.service.impl.HelloServiceImpl : 当前线程:task-2
这跟我想象不同,如果Spring会默认使用SimpleAsyncTaskExecutor线程池,不是应该会一直创建线程吗,这里为什么最多只会创建8个线程呢?
首先我在spring的文档中找到了TaskExecutor 的各类型描述:
- SyncTaskExecutor: This implementation does not run invocations asynchronously. Instead, each invocation takes place in the calling thread. It is primarily used in situations where multi-threading is not necessary, such as in simple test cases.
- SimpleAsyncTaskExecutor: This implementation does not reuse any threads. Rather, it starts up a new thread for each invocation. However, it does support a concurrency limit that blocks any invocations that are over the limit until a slot has been freed up. If you are looking for true pooling, see ThreadPoolTaskExecutor, later in this list.
- ConcurrentTaskExecutor: This implementation is an adapter for a java.util.concurrent.Executor instance. There is an alternative (ThreadPoolTaskExecutor) that exposes the Executor configuration parameters as bean properties. There is rarely a need to use ConcurrentTaskExecutor directly. However, if the ThreadPoolTaskExecutor is not flexible enough for your needs, ConcurrentTaskExecutor is an alternative.
- ThreadPoolTaskExecutor: This implementation is most commonly used. It exposes bean properties for configuring a java.util.concurrent.ThreadPoolExecutor and wraps it in a TaskExecutor. If you need to adapt to a different kind of java.util.concurrent.Executor, we recommend that you use a ConcurrentTaskExecutor instead.
- WorkManagerTaskExecutor: This implementation uses a CommonJ WorkManager as its backing service provider and is the central convenience class for setting up CommonJ-based thread pool integration on WebLogic or WebSphere within a Spring application context.
- DefaultManagedTaskExecutor: This implementation uses a JNDI-obtained ManagedExecutorService in a JSR-236 compatible runtime environment (such as a Java EE 7+ application server), replacing a CommonJ WorkManager for that purpose.
SimpleAsyncTaskExecutor描述翻译过来的意思是:该实现不重复使用任何线程。相反,它为每次调用启动一个新线程。然而,它支持并发限制,可以阻止任何超过限制的调用,直到释放出一个插槽。如果您正在寻找真正的线程池,请参阅本列表后面的 ThreadPoolTaskExecutor。
大概意思就是这个并不是严格意义上的线程池,每次调用都会启动一个新线程,但是支持并发,还是推荐我们使用ThreadPoolTaskExecutor。
我们再来看看ThreadPoolTaskExecutor的解释:该实现最常用。它公开了用于配置java.util.concurrent.ThreadPoolExecutor的Bean属性,并将其封装在TaskExecutor中。如果您需要适应不同类型的java.util.concurrent.Executor,我们建议您使用ConcurrentTaskExecutor来代替。
秉承着打破沙锅问到底的精神,我去翻阅了springboot的源码,在TaskExecutionAutoConfiguration类中,看到了如下代码:
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.task;
import java.util.concurrent.Executor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Shutdown;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.boot.task.TaskExecutorCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link TaskExecutor}.
*
* @author Stephane Nicoll
* @author Camille Vienot
* @since 2.1.0
*/
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {
/**
* Bean name of the application {@link TaskExecutor}.
*/
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
@Bean
@ConditionalOnMissingBean
public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
ObjectProvider<TaskDecorator> taskDecorator) {
TaskExecutionProperties.Pool pool = properties.getPool();
TaskExecutorBuilder builder = new TaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
builder = builder.taskDecorator(taskDecorator.getIfUnique());
return builder;
}
@Lazy
@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
}
在applicationTaskExecutor方法中进入到了TaskExecutorBuilder类,看到了
public ThreadPoolTaskExecutor build() {
return this.configure(new ThreadPoolTaskExecutor());
}
发现默认使用的并不是SimpleAsyncTaskExecutor而是ThreadPoolTaskExecutor。
最后查询了各种资料才找到了原因,在spring boot2.1.0.RELEASE版本之前,默认用的确实是SimpleAsyncTaskExecutor线程池,在源码(spring boot2.0.9.RELEASE)AsyncExecutionInterceptor类中如下:
@Nullable
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
return (Executor)(defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
也就是说新版本,其实spring boot默认使用的已经是ThreadPoolTaskExecutor线程池了,大家不用再去手动更改默认的线程池,不过还是可以在配置文件更改ThreadPoolTaskExecutor的参数:
#核心线程数
spring.task.execution.pool.core-size=10
#最大线程数
spring.task.execution.pool.max-size=1000
#空闲线程保留时间
spring.task.execution.pool.keep-alive=3s
#队列容量
spring.task.execution.pool.queue-capacity=1000
#线程名称前缀
spring.task.execution.thread-name-prefix=thread-prefix
具体参数根据实际情况而定。