비동기적인 방식은
A가 B에게 작업을 요청하고, B의 작업이 실행되는 것과 상관없이 A 자신의 작업을 이어서 진행하는 방식을 의미한다.
동기/비동기에 대한 구체적인 설명은 아래 글에 정리하였었다.
동기/비동기(Sync/Async), Blocking/Non-Blocking
시작하기에 앞서, 간단히 기준이 되는 용어를 보겠습니다.Blocking/Non-Blocking : '제어권'을 두고 구분동기/비동기 : 결과값을 기다리느냐로 구분 1. Blocking VS Non-Blocking : 제어의 관점요청한 작업이 전
thisisprogrammingworld.tistory.com
Spring Boot에서는 몇개의 애노테이션 설정만으로 비동기 방식을 구현할 수 있도록 제공한다.
- @EnableAsync, @Async 이용
1. 설정 클래스에 @EnableAsync 적용
@Configuration 애노테션이 붙은 설정 클래스에 적용해주어야 비동기 기능이 활성화된다.
보통 해당 클래스 내에 비동기 작업을 처리하는 스레드 풀에 대한 설정도 함께 해준다.
@EnableAsync
@Configuration
public class AsyncConfig {
/*
비동기 작업들 실행 시 사용될 Thread Pool
모두 Default 값으로 설정
*/
@Bean
public Executor asyncThreadPoolExecutor() {
ThreadPoolTaskExecutor executer = new ThreadPoolTaskExecutor();
executer.setCorePoolSize(1);
executer.setMaxPoolSize(Integer.MAX_VALUE);
executer.setQueueCapacity(Integer.MAX_VALUE);
executer.setThreadNamePrefix("AsyncThread-");
executer.initialize();
return executer;
}
}
일반적으로, Spring 은 SimpleAsyncTaskExecutor 클래스를 사용해 비동기 처리를 수행한다.
다만, 아래 두가지 방법으로 오버라이딩할 수 있다.
- 애플리케이션 레벨에서 오버라이딩 : AsyncConfigurer 인터페이스를 implements하면서 getAsyncExecutor() 메서드를 오버라이딩하고, 이 메서드 내에서 원하는 구현체를 반환하도록 한다.
- 개별 메서드 레벨에서 오버라이딩 : @Async 애노테이션의 name 속성을 이용하여 특정 Bean 이름을 지정할 수 있다. 이 애노테이션이 붙은 메서드에서는 지정된 클래스에서 관리되는 스레드 풀을 이용하게 된다. ex) @Async("threadPoolTaskExecutor")
ThreadPoolTaskExecutor 는 일반 요청 시에는 사용되지 않는 스레드 풀이고
비동기 메서드를 수행할 때 (@Async를 적용하여 비동기 작업을 수행하려할 때, @EventListener를 적용한 비동기 이벤트 리스너가 수행될 때), 혹은 직접 이 Executor를 사용하겠다고 정의한 경우 사용된다.
Spring Boot 3.x 에서의 ThreadPoolTaskExecutor 설정 방법
Spring Boot 3점대 를 사용한다면, ThreadPoolTaskExecutor 를 직접 설정하지 않고 설정 파일에 입력하는 것만으로도 설정 가능하며, Spring Boot가 자동으로 관리해준다.
다만, 여러 스레드 풀의 설정을 해줘야한다면 별도 설정 클래스에서 빈을 등록해주는 것이 관리 측면에서 좋을 것으로 보인다.
# application.yml
spring:
task:
execution:
pool:
core-size: 5
max-size: 10
queue-capacity: 100
thread-name-prefix: "Async-"
ThreadPoolTaskExecutor 클래스 에 대해 설정할 수 있는 값들은 아래와 같다.
- spring.task.execution.pool.core-size: core thread 갯수. 가상 스레드가 활성화되어 있으면 의미 없음. (Default : 8)
- spring.task.execution.pool.max-size: 늘릴 수 있는 최대 스레드 갯수. 큐가 가득 차버리면 (즉, 100개의 작업이 대기 중인 상황) 스레드 풀은 최대 16개까지 증가하도록 하여 부하를 수용할 수 있다. 다만 큐 용량이 무한대라면 무시되는 값이다. (Default : Integer 범위 최대 값)
- spring.task.execution.pool.queue-capacity : 큐 용량. 이 용량이 무제한이라면, max-size 속성이 무시된다. (Default : Integer 범위 최대 값)
- spring.task.execution.pool.keep-alive : 스레드가 회수되기 전 유휴 상태로 유지하는 시간 제한. 10초 설정하면, 10초 동안 유휴 상태가 유지되면 회수된다. (Default : 60s)
2. 비동기로 수행시키고 싶은 메서드에 @Async 적용
간략히 0부터 9까지 순회 출력하는 메서드를 작성하였다.
- 비동기 메서드는 public 접근제어자 로 구현되어야 한다.
- Spring에서는 AOP 기반의 프록시 패턴을 이용하여 프록시 객체를 통해 비동기 메서드에 접근한다.
프록시 객체를 거치지 않고 직접 메서드를 호출할 경우 비동기적으로 동작하지 않는다. - 물론 동일한 클래스 내부에서 해당 메서드를 직접 호출할 경우에도 비동기적으로 동작하지 않는다.
- Spring에서는 AOP 기반의 프록시 패턴을 이용하여 프록시 객체를 통해 비동기 메서드에 접근한다.
- 같은 맥락으로, 자기 호출 (self-invocation) 하는 경우에도 비동기적으로 동작하지 않는다.
@Component
public class AsyncTask {
@Async("asyncThreadPoolExecutor")
public void async() {
for (int i=0; i<10; i++) {
log.info("async() - {}", i);
}
log.info("2. async() Method Finished. (time: {})", LocalTime.now());
}
}
위 메서드를 호출하는 테스트코드를 작성하여,
테스트 코드에서 async() 메서드를 호출 했을 때 async() 메서드 호출이 완료되기 전에
테스트 코드 내 다음 로직이 실행되는지 확인하였다.
@SpringBootTest
class AsyncTaskTest {
@Autowired
private AsyncTask asyncTask;
@Test
void async() {
System.out.println("1. Before async() Calling : " + LocalTime.now());
asyncTask.async();
System.out.println("3. async() finished : " + LocalTime.now());
}
}
동기적으로 처리된다면 1 > 2 > 3. 순으로 메세지가 출력되지만, 비동기로 처리되어 1 > 3 > 2. 순으로 메세지가 출력되는 것을 확인하였다.

참고
- https://docs.spring.io/spring-boot/reference/features/task-execution-and-scheduling.html
- https://www.baeldung.com/spring-async
'백엔드 개발하며 작성한 > Spring' 카테고리의 다른 글
| [Spring Batch] 1. 스프링 배치 공부 순서와 간단 소개 (0) | 2023.06.11 |
|---|---|
| Spring Framework와 Spring Boot (0) | 2022.08.01 |
| @Component에 대해 (@Bean) (0) | 2022.07.26 |
| @RequestBody 붙였어?? (0) | 2022.07.13 |
| Spring Boot에서 Connection Pool 이용하기 (HikariCP) (0) | 2022.04.10 |