@Async 를 사용하기 위해, @EnableAsync 어노테이션을 Configuration 클래스에 붙여준다.
Configuration 설정
- TaskExecutor 설정을 Bean으로 한다.
- 다수의 TaskExecutor 를 설정하고, Async 어노테이션에서 골라서 실행 할 수 있다.
- cpu를 많이 사용하는 작업인지, I/O 작업이 많은지 등에 따라 설정 하고 골라 사용 가능.
- 혹은, application.yaml 에서도 가능하다.
@Configuration
@EnableAsync
public class AsyncConfig {
/**
* 가벼운 비동기 작업용
* (알림, 로그, 이벤트 후처리 등)
*/
@Bean(name = "lightTaskExecutor")
public Executor lightTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("async-light-");
executor.initialize();
return executor;
}
/**
* 무거운 비동기 작업용
* (외부 API 호출, 파일 처리, 배치성 작업 등)
*/
@Bean(name = "heavyTaskExecutor")
public Executor heavyTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("async-heavy-");
executor.initialize();
return executor;
}
}
- SimpleAsyncTaskExecutor, ThreadPoolTaskExecutor 등이 있다.
- ThreadPoolTaskExecutor 사용을 권장한다.
- setCorePoolSize : 기본적으로 유지되는 스레드 수
- maxPoolSize : 최대 생성 가능한 스레드 수
- setQueueCapacity : 작업대기 큐 개수
- keepAliveSeconds : 유휴 스레드 유지시간 설정.
- threadNamePrefix : 스레드 이름 식별을 위한 접두사
- ThreadPoolTaskExecutor 사용을 권장한다.
기본 Default 용 Async 설정 (AsyncConfigurer)
- @Async 에서 특정 Executor를 지정하지 않을경우, 이 설정에서 명시한 Executor가 동작하게 된다.
- 이 설정없이 @Async 사용시 Executor를 지정하지 않으면, 그냥 비동기 동작한다. Executor를 사용하지 않는다.
- Exception 관련 처리도 할 수 있다.
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 기본 스레드
executor.setMaxPoolSize(10); // 최대 스레드
executor.setQueueCapacity(100); // 대기 큐
executor.setThreadNamePrefix("async-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(10);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler
getAsyncUncaughtExceptionHandler() {
return new GlobalAsyncExceptionHandler();
}
}
- AsyncUncaughtExceptionHandler 의 경우 https://dean83.tistory.com/317 를 참조한다.
@Async 를 이용한 비동기 정의
- 메서드, 혹은 클래스에 붙일 수 있는 어노테이션으로, 비동기로 동작함을 정의할때 사용한다.
- 클래스에 붙이면 하위 모든 동작이 비동기
- 메서드에 붙이면 해당 메서드만 비동기
- 주의점 : 같은 클래스 내 에서 동기, 비동기 메서드가 별개로 있는 상황에서, 동기 메서드에서 비동기 메서드를 호출해도 비동기 동작하지 않는다.
- 외부 -> 빈 주입 -> 주입된 빈의 비동기 메서드 호출시, proxy를 통해 실행되므로 비동기 작업이 수행된다.
- 내부 -> 비동기 메서드 호출시, proxy를 통하지 않게 되므로 비동기 작업이 수행되지 않는다.
- 따라서, 비동기 메서드들을 모아 별도의 서비스로 구현하고, 타 서비스에서 이 서비스를 이용하도록 하는것이 좋다.
@Service
public class NotificationService {
@Async("lightTaskExecutor")
public void sendNotification(String message) {
System.out.println(
"light thread = " + Thread.currentThread().getName()
);
}
@Async("heavyTaskExecutor")
public void processFile(String filePath) {
System.out.println(
"heavy thread = " + Thread.currentThread().getName()
);
}
}
- 대부분의 경우, Async 어노테이션이 붙은 메서드를 작성할때, 일반적인 코드 구현 방식으로 작성 하면 된다.
- 만일 비동기 작업의 결과값을 반환받아 무언가 해야 한다면, CompletableFuture를 이용할 수도 있다.
트랜잭션 컨텍스트 관련
트랜잭션은 하나의 스레드에서만 동작하므로, 비동기 작업을 하게 되면 기존 트랜잭션은 이용할 수 없다.
따라서, A 메서드에서 트랜잭션을 이용하여 작업을 하다가, B 메서드를 비동기 작업으로 호출하게 되면, 트랜잭션 전파가 되지 않는다. @Transactional(proparation = Propagation.REQURES_NEW) 어노테이션을 @Async와 같이 이용해야 한다.
(비동기 이벤트에서 한번 정리한 적이 있다. https://dean83.tistory.com/320)
비동기 작업시, 동시성 관련은 https://dean83.tistory.com/392 를 참조한다.
Actuator를 이용한 ThreadPool 모니터링
- 프로메테우스 라이브러리를 추가 해야함
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
}
- application.yaml 설정
management:
endpoints:
web:
exposure:
include: health,metrics,threaddump
metrics:
export:
prometheus:
enabled: true
- 실시간으로 스레드풀 상태를 확인하기 위해 커스터마이징
package com.example.actuator;
import org.springframework.boot.actuator.endpoint.annotation.Endpoint;
import org.springframework.boot.actuator.endpoint.annotation.ReadOperation;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@Endpoint(id = "threadpool")
public class ThreadPoolEndpoint {
private final ThreadPoolTaskExecutor taskExecutor;
public ThreadPoolEndpoint(ThreadPoolTaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
@ReadOperation
public Map<String, Object> threadPoolInfo() {
return Map.of(
"activeCount", taskExecutor.getActiveCount(),
"poolSize", taskExecutor.getPoolSize(),
"corePoolSize", taskExecutor.getCorePoolSize(),
"maxPoolSize", taskExecutor.getMaxPoolSize(),
"queueSize", taskExecutor.getThreadPoolExecutor().getQueue().size()
);
}
}
혹은 scheduler를 이용하여 코드로 상태를 확인 할 수도 있다. (스케줄러 사용 : https://dean83.tistory.com/373)
'Backend > SpringBoot' 카테고리의 다른 글
| MVC 환경에서 WebClient (0) | 2026.01.05 |
|---|---|
| Task Decorator (@Async 에서 MDC, SecurityContextHolder 정보 활용) (0) | 2026.01.02 |
| [Spring Security] SessionID 관련 주요 필터, 이벤트처리 및 설정 (0) | 2025.12.17 |
| [Spring Security] CORS 설정 (0) | 2025.12.16 |
| [Spring Security] CSRF 토큰 사용 (+CORS) (0) | 2025.12.16 |