✅为什么不建议直接使用Spring的@Async
典型回答
@Async中关于线程池的使用部分在AsyncExecutionInterceptor中,在这个类中有一个getDefaultExecutor方法, 当我们没有做过自定义线程池的时候,就会用SimpleAsyncTaskExecutor这个线程池。
@Override
protected Executor getDefaultExecutor(BeanFactory beanFactory) {
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}但是这里需要注意的是,他并不是无脑的的直接创建一个新的,这部分在扩展知识中讲
SimpleAsyncTaskExecutor这玩意坑很大,其实他并不是真的线程池,它是不会重用线程的,每次调用都会创建一个新的线程,也没有最大线程数设置。并发大的时候会产生严重的性能问题。
他的doExecute核心逻辑如下:
/**
* 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();
}所以,我们应该自定义线程池来配合@Async使用,而不是直接就用默认的。
扩展知识
自定义线程池
我们可以通过如下方式,自定义一个线程池:
例子用的是这篇的改造的:
✅在Spring中如何使用Spring Event做事件驱动
@Configuration
@EnableAsync
public class AsyncExecutorConfig {
@Bean("registerSuccessExecutor")
public Executor caseStartFinishExecutor() {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("registerSuccessExecutor-%d").build();
ExecutorService executorService = new ThreadPoolExecutor(100, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
return executorService;
}
}并且把他声明为@Configuration,然后也可以把Application.java中的 @EnableAsync放到这里。
接下来在使用@Async的时候,指定一下即可:
/**
* 案件中心内部事件监听器
*
* @author Hollis
*/
@Component
public class RegisterEventListener {
@EventListener(RegisterSuccessEvent.class)
@Async("registerSuccessExecutor")
public void onApplicationEvent(RegisterSuccessEvent event) {
RegisterInfo registerInfo = (RegisterInfo) event.getSource();
//执行发送欢迎短信的逻辑
//这里注意要控制好并发,这个例子就不细说了,可以参考:✅基于Redis的分布式锁,解决短信验证码重复发放等问题
}
}在@Async中指定registerSuccessExecutor即可。这样在后续执行时,就会用到我们自定义的线程池了。
不是无脑创建SimpleAsyncTaskExecutor
在getDefaultExecutor的实现中,并不是一上来就直接new SimpleAsyncTaskExecutor()的,而是先尝试着获取默认的执行器。
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}看一下这个super.getDefaultExecutor(beanFactory);代码:
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
if (beanFactory != null) {
try {
// Search for TaskExecutor bean... not plain Executor since that would
// match with ScheduledExecutorService as well, which is unusable for
// our purposes here. TaskExecutor is more clearly designed for it.
return beanFactory.getBean(TaskExecutor.class);
}
catch (NoUniqueBeanDefinitionException ex) {
logger.debug("Could not find unique TaskExecutor bean. " +
"Continuing search for an Executor bean named 'taskExecutor'", ex);
try {
return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskExecutor bean found within the context, and none is named " +
"'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " +
"as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
logger.debug("Could not find default TaskExecutor bean. " +
"Continuing search for an Executor bean named 'taskExecutor'", ex);
try {
return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
}
catch (NoSuchBeanDefinitionException ex2) {
logger.info("No task executor bean found for async processing: " +
"no bean of type TaskExecutor and no bean named 'taskExecutor' either");
}
// Giving up -> either using local default executor or none at all...
}
}
return null;
}简单总结一下,就是会先尝试获取TaskExecutor的实现类,这里如果能且仅能找到唯一一个,那么就用这个,如果找不到,或者找到了多个,那么就会走到catch (NoUniqueBeanDefinitionException ex) 和catch (NoSuchBeanDefinitionException ex)的分支中,这里就是获取beanName为taskExecutor的Bean
以上查询如果还是没查到,那么再创建SimpleAsyncTaskExecutor。
所以,也就是说Spring给了我们一个机会,他也知道SimpleAsyncTaskExecutor这玩意性能不行,所以他会尝试获取一个我们自定义的线程池,如果我们定义了一个,那么他就会用我们定义的这个,如果我们定义了多个,那么他就不用了。
所以,这也是为什么有的时候我们自定义了一个线程池,但是没有在@Async中指定,他也能用到他的原因,但是这种方式并不靠谱,万一后面又新定义了一个,那就凉凉了。或者我们可以把一个线程池的BeanName设置成taskExecutor也行。