@EnableAsync, TaskExecutor, 크아악
- Spring 비동기 처리의 내부 구조, 동작 원리
- @EnableAsync, 설정법에 대해
- TaskExecutor와 스레드 관리 구조 이해
- Spring이 비동기 메서드를 어떤 방식으로 실행하는가
- 테스트
비동기 처리 방식
@Async 애너테이션을 기반으로 한 고수준 비동기 처리 메커니즘
Java에서 제공해주는 CompletableFuture와 달리, Spring은 스레드 관리, 예외 처리, 트랜잭션 분리를 자동으로 지원하여 더욱 쉽게 비동기 로직을 구현할 수 있다.
하지만.. Spring은 프레임워크다. 잊지말자. Spring을 벗겨내면 결국 Java의 CompletableFuture 등을 통해서도 구현 가능함
1. 비동기 처리 아키텍처
Spring에서 비동기 처리를 활성화하면, 내부적으로 프록시(Proxy) 객체가 생성되어 비동기 메서드를 별도의 스레드에서 실행함
클라이언트의 호출 - > @Async 메서드가 호출됨 - > Spring 프록시가 생성됨 - > TaskExecutor을 전달?
-> 스레드풀 내부에서 비동기 실행 - > 결과를 반환함 : Future/CompletableFuture
1) ☆핵심 구성 요소☆
| @Async | 메서드를 비동기로 실행하도록 표시함 |
| @EnableAsync | 비동기 기능 활성화(프록시 생성) |
| TaskExecutor | 비동기 스레드를 관리하는 실행기 |
| ThreadPoolTaskExecutor | 실무에서 가장 많이 사용하는 스레드풀 구현체 |
| AsyncUncaughtExceptionHandler | 비동기 스레드의 예외 처리 담당 |
2. @EnableAsync 활성화
비동기 기능을 사용하려면, 먼저 @EnableAsync를 통해 Spring 컨테이너에 비동기 기능을 등록해야함
@Configuration
@EnableAsync // 이걸 통해서 비동기 처리 기능을 활성화한다
public class AsyncConfig {
}
@EnableAsync 애너테이션은 내부적으로 AsyncAnnotationBeanPostProcessor를 등록하여, @Async가 붙은 메서드를 프록시로 감싸고 비동기 스레드 풀에 위임할 수 있게 만듬
* @EnableAsync는 SpringBoot에서 자동 활성화되지 않는다. 반드시 명시적으로 추가해줘야함
3. 비동기 실행의 기본 흐름
클라이언트가 컨트롤러에게 요청 전송 - > 컨트롤러가 서비스에서 @Async 메서드를 호출, 이후 프록시 처리로 즉시 반환
이하는 프록시 처리로 즉시 반환하는 과정..이라고 본다, 비동기니까 다른 로직이 수행됨과 동시에 다른 스레드풀에서 실행되니까
- > @Async 서비스가 TaskExecutor에 비동기 작업을 위임함 - > TaskExecutor가 스레드 풀에서 작업을 수행함
- > TaskExecutor가 @Async 서비스에게 작업 완료를 알림
* @Async 메서드는 호출 즉시 반환되며, 별도의 스레드에서 실제 작업이 수행됨
* 스레드풀(TaskExecutor)을 통해 병렬로 실행되므로, 메인 스레드는 블로킹되지 않는다.(논블로킹인가?)
4. 기본 스레드 관리 구조
Spring은 기본적으로 SimpleAsyncTaskExecutor를 사용하지만, 실무에서는 대부분 ThreadPoolTaskExecutor로 커스터마이징해서 사용한다고 한다.
기본 TaskExecutor 등록 구조
src\main\java\com.example.async
- config\AsyncConfig.java
- service\AsyncService.java
- controller\AsyncController.java
예제
AsyncConfig.java
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4); // 기본 스레드 갯수
executor.setMaxPoolSize(8); // 최대 스레드의 갯수임
executor.setQueueCapacity(100); // 대기열의 크기, 큐
executor.setThreadNamePrefix("AsyncExecutor-"); // 스레드 이름 접두사
executor.initialize();
return executor;
}
}
AsyncService.java
@Service
public class AsyncService {
@Async("taskExecutor") // 지정된 Executor를 자용하게끔 함
public void sendEmail(String to) {
System.out.println(Thread.currentThread().getName() + "이메일 발송 중" + to);
try {
Thread.sleep(1500); // 처리 시간 시뮬레이션
} catch(InterruptedException e) {
e.PrintStackTrace();
}
System.out.println(Thread.currentThread().getName() + "이메일 발송 완료" + to);
}
}
AsyncController.java
@RestController
@RequiredArgsConstructor
public class AsyncController {
private final AsyncService asyncService;
@GetMapping("/email");
public String sendEmail() {
asyncService.sendEmail("b1uffer@example.com");
return "이메일 접수 완료!"; // 즉시 반환됨
}
}
* 컨트롤러는 즉시 응답을 사용자에게 반환해주고, 비동기 메서드는 별도의 스레드에서 실행된다.
팁
비동기 환경 구성할 때 주의사항
- @EnableAsync를 반드시 추가해줘야 비동기 기능이 활성화된다.
- @Async는 public 메서드에서만 적용되며, 같은 클래스 내의 자기 호출(Self-invocation)에서는 동작하지 않는다.
- Executor 설정시, 스레드풀 크기를 과도하게 설정하면 오히려 성능 저하가 발생할 수 있음
- 예외 처리를 위해 AsyncUncaughtExceptionHandler를 별도로 구성해주는게 좋다고 한다.
- 트랜잭션 전파가 필요한 경우, 비동기 실행 전에 트랜잭션을 완료해줘야 한다고 한다.
정리
| 구분 | 내용 |
| 비동기 실행의 원리 | 프록시 객체가 메서드 호출을 TaskExecutor에 위임해서 별도의 스레드에서 실행함 |
| 핵심 설정 | @EnableAsync, @Async, TaskExecutor |
| 기본 스레드풀 | SimpleAsyncTaskExecutor(기본), ThreadPoolTaskExecutor(실무) |
| 주의사항 | 자기호출시 비동기 미작동, 예외 전파 불가, 스레드풀 과도 설정 주의 |
'Spring Boot > 비동기 처리' 카테고리의 다른 글
| 비동기 처리 : @Async 활용 (0) | 2025.11.02 |
|---|---|
| 비동기 처리 : 비동기 처리를 왜 해야하는가 (0) | 2025.11.01 |
| Spring Event : 분산 환경으로의 확장 가능성 (0) | 2025.10.29 |
| Spring Event : 이벤트 기반 비동기 처리 활용사례 (0) | 2025.10.29 |
| Spring Event : 비동기 이벤트 처리 구현 (0) | 2025.10.25 |