스프링에서 Async를 사용하는 경우 @EnableAsync
어노테이션의 주석에서 아래와 같은 내용을 확인할 수 있습니다.
By default, Spring will be searching for an associated thread pool definition:
either a unique org.springframework.core.task.TaskExecutor bean in the context,
or an java.util.concurrent.Executor bean named "taskExecutor" otherwise.
If neither of the two is resolvable, a org.springframework.core.task.SimpleAsyncTaskExecutor will be used to process async method invocations.
Besides, annotated methods having a void return type cannot transmit any exception back to the caller.
By default, such uncaught exceptions are only logged.
To customize all this, implement AsyncConfigurer and provide:
TaskExecutor를 구현한 빈을 찾지 못하면 SimpleAsyncTaskExecutor
를 사용한다고 되어 있습니다.
// NOTE: This implementation does not reuse threads! Consider a thread-pooling TaskExecutor implementation instead, in particular for executing a large number of short-lived tasks.
public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
implements AsyncListenableTaskExecutor, Serializable {
}
SimpleAsyncTaskExecutor
클래스에는 스레드를 재사용하지 않는다고 적혀있어 해당 Executor를
사용하면 문제가 될 것 같은데 실제로 아무런 설정을 넣지 않아도 SimpleAsyncTaskExecutor
를 사용하지 않습니다.
이는 TaskExecutionAutoConfiguration
에 의해 ThreadPoolTaskExecutor
가 생성되기 때문입니다.
spring.factories
에 아래와 같은 내용이 등록되어 있습니다.
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
@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();
}
}
기본적으로 생성되는 ThreadPoolTaskExecutor
는 Async 이외에 다른 스케줄링에도 사용되므로 @Async
사용 시 AsyncConfigurerSupport
를 통해 비동기 작업을 위한 스레드풀을 별도로 정의해주는게 좋습니다.