🎯 SimpleAsyncTaskExecutor vs ThreadPoolTaskExecutor
@Async 애노테이션은 Spring 에서 제공하는 비동기(Asynchronous) 작업을 실행할 때 사용하는 어노테이션 입니다. 즉, 메서드를 별도의 스레드에서 실행하도록 만들어서 메인 스레드가 해당 작업을 기다리지 않고 바로 다음 작업을 실행할 수 있도록 합니다.
💡 비동기 작업이란?
- 현재 실행 중인 메서드의 흐름(메인 스레드)과 별개로 실행되는 작업
- 예: 이메일 전송, 파일 업로드, 데이터 처리 등
이러한 @Async 애노테이션을 활용한 비동기 처리는 스레드 풀을 사용해야 훨씬 효율적으로 자원을 관리할 수 있습니다. 하지만 @Async 를 사용할때 아무 설정도 하지 않으면 Spring 은 기본적으로 SimpleAsyncTaskExecutor 을 사용하는데 이는 쓰레드 개수를 관리하지 않습니다. 이때 쓰레드 개수를 관리하지 않음은 새로운 스레드를 계속 생성 하는 것을 의미 합니다.
때문이 우리는 쓰레드 풀을 사용하기 위해 ThreadPoolTaskExecutor 를 설정하고 보다 효율적으로 쓰레드 개수를 관리해야 합니다.
이처럼 스레드 풀을 이용한 비동기 처리 방법은 다음과 같은 장점을 갖습니다.
1. 자원 효율성 : 스레드 풀은 미리 정해진 개수의 스레드를 생성해 관리하기 때문에 스레드 생성 및 삭제에 따른 오버헤드를 줄일 수 있습니다.
2. 응답성 및 처리량 향상 : 스레드 풀은 작업을 대기 상태로 유지하여 처리속도를 향상시킬 수 있습니다. 즉, 작업이 발생하면 대기 중인 스레드 중 하나를 선택하여 작업을 할당하므로, 작업 처리를 병렬로 진행할 수 있습니다.
🎯 비동기 처리 구현 방법
✅ applicaiton.yml
thread:
pool:
core-pool-size: 4
queue-capacity: 50
max-pool-size: 8
스레드 풀을 직접 제어하기 위한 값들을 지정할 수 있습니다. CORE_POOL_SIZE 는 기본적으로 유지되는 스레드 개수, MAX_POOL_SIZE 는 최대 생성할 수 있는 스레드 개수, QUEUE_CAPACITY 는 처리 대기중인 작업을 저장할 큐 크기를 나타냅니다.
✅ AsyncConfig
package com.common.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Value("${thread.pool.core-pool-size}")
private int CORE_POOL_SIZE;
@Value("${thread.pool.max-pool-size}")
private int MAX_POOL_SIZE;
@Value("${thread.pool.queue-capacity}")
private int QUEUE_CAPACITY;
private static final Logger logger = LogManager.getLogger(AsyncConfig.class);
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandlerImpl();
}
static class AsyncUncaughtExceptionHandlerImpl implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
logger.error(ex.getMessage());
logger.error("Method name - {}", method.getName());
for (Object param : params) {
logger.error("Parameter value - {}", param);
}
}
}
}
AsyncConfig 는 Spring 에서 비동기 실행을 설정하는 클래스 입니다. Spring 에서 @EnableAsync 를 사용하면 @Async 가 붙은 메서드를 별도의 스레드 풀에서 실행할 수 있습니다. 이 클래스는 비동기 작업을 실행할 스레드풀을 설정하고 예외처리를 담당하는 역할을 합니다.
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.initialize();
return executor;
}
이 메서드는 비동기 작업을 실행할 스레드 풀을 생성헙니다. ThreadPoolTaskExecutor 을 이용해 스레드 풀에 대한 설정을 합니다. 이 설정으로 인해 @Async 는 쓰레드 풀을 사용해 자원을 효율적으로 관리합니다.
즉, 작동 방식을 정리하면 다음과 같습니다.
(1) 처음에는 4개의 스레드를 생성합니다.
(2) 동시에 실행할 작업이 많아지면, 최대 8개 까지의 스레드를 추가 생성합니다.
(3) 스레드가 부족하면 최대 50 개의 작업을 큐에 대기합니다.
(4) 큐도 꽉 차면, 새로운 요청이 거부되거나 예외가 발생할 수 있습니다.
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandlerImpl();
}
static class AsyncUncaughtExceptionHandlerImpl implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
logger.error(ex.getMessage());
logger.error("Method name - {}", method.getName());
for (Object param : params) {
logger.error("Parameter value - {}", param);
}
}
}
비동기 메서드 실행 중 예외가 발생하면 Spring 에서는 기본적으로 예외를 인지하지 못 합니다. 때문에 예외처리를 실행시킬 구현체 (AsyncUncaughtExceptionHandlerImpl) 을 구현해서 비동기 작업 중 발생한 예외를 로깅하도록 구현했습니다.
🎯 정리
지금까지 Spring 에서 비동기 처리를 구현하는 방법에 대해 알아봤습니다. 기본적으로 Spring 에서 @Async 를 이용한 비동기 처리는 스레드 풀에서 관리되며 직접 스레드를 관리해 작업을 제어할 수 있습니다.
이러한 스레드 풀을 이용한 비동기 처리 방식은 스레드를 재활용 하기 때문에 성능 향상에 도움이 됩니다. 즉, 할당된 일을 마친 스레드는 바로 소멸되지 않고 스레드 풀에 잠시 저장해뒀다 또 필요할때 꺼내 쓰기 때문입니다.
저는 직접 쓰레드 개수를 관리하도록 설정했지만 Spring 에서 @Async 만 붙여 사용하다면 내부적으로 SimpleAsyncTaskExecutor가 사용됩니다. 이는 결국 쓰레드를 관리하지 않아 실제 서비스를 운영할때 매우 비 효율적으로 다가올 수 있습니다. (쓰레드가 계속 생성 됨)
때문에 Spring 에서 @Async 비동기 처리를 사용할때 ThreadPoolTaskExecutor 을 사용해 쓰레드 풀에 대한 설정을 해주는 것이 성능상 훨씬 효율적입니다.
'Spring > Spring' 카테고리의 다른 글
[Spring] Spring 과 STOMP 웹소켓 채팅 기능 구현 (1) | 2025.03.12 |
---|---|
[Spring] 커스텀 Resolver Exception 로 예외 관리하기 (0) | 2025.01.25 |
[Spring] 연관관계를 포함한 객체 MapStruct 사용법 (0) | 2024.04.17 |
[Spring] Spring Security + OAuth2.0 + Jwt 예제 구현 (0) | 2024.03.29 |
[Spring] Spring Security 와 Jwt 를 이용한 회원가입, 로그인 구현 (0) | 2024.03.24 |