🔹기본 개념
🔸동기(Synchronous)와 비동기(Asynchronous)
- 동기: 요청한 작업을 들어온 순서대로 처리.
- 비동기: 요청한 작업을 순서에 상관없이 처리.
🔸Blocking 과 Non-Blocking
- Blocking: 요청한 작업이 끝날 때까지 제어권을 넘김.
- Non-Blocking: 요청한 작업이 끝나지 않아도 제어권을 계속 유지.
🔸싱글 스레드와 멀티 스레드
- 싱글 스레드: 하나의 프로세스에서 하나의 쓰레드가 작업 수행.
- 멀티 스레드: 하나의 프로세스에서 여러 개의 쓰레드가 작업 수행.
Code, Data, Heap 영역 공유은 공유하고 stack 영역은 각 쓰레드가 별도로 관리하기 때문에 동시성 문제가 발생할 수 있어 주의해야한다.
🔸조합에 따른 동작 방식
- 싱글 스레드 - 동기: 하나의 공간에서 순서대로 처리.
- 싱글 스레드 - 비동기: 하나의 공간에서 순서 관계없이 처리.
- 멀티 스레드 - 동기: 여러 개의 공간에서 순서대로 처리.
- 멀티 스레드 - 비동기: 여러 개의 공간에서 순서 관계없이 처리.
🔹비동기 처리 구현 - @Async, @EnableAsync
@Async는 비동기 처리를 위해 사용하는 어노테이션이다. 해당 어노테이션을 사용하기 위해서는 @EnableAsync 가 달려있는 configuration 클래스가 필요하다. 해당 내용을 공부하기 전, @Async와 프록시 객체에 대해 정리했다.
🔸 @Async 동작 방식
진행과정은 다음과 같다.
- @Async 어노테이션이 붙은 메서드가 호출되면, 스프링은 해당 호출을 가로채서 비동기 실행을 처리하기 위한 프록시 객체를 생성한다.
- 해당 메서드는 TaskExecutor를 사용하여 비동기 작업을 실행할 스레드를 할당한다.
- 해당 메서드는 호출자와 별도의 스레드에서 작업이 진행되며, 호출자 메서드는 Blocking되지 않고 즉시 리턴된다.
이 어노테이션은 public 메서드에서만 적용 가능하며, self-invocation(자기자신의 메서드를 호출하는 것)은 불가하다.
🔸 @EnableAsync의 사용
@EnableAsync은 스프링 컨텍스트에 비동기 처리를 위한 관련 빈들을 등록한다.
@SpringBootApplication이 사용된 클래스에 @EnableAsync를 사용하게 되면, @SpringBootApplication에 포함된 @Configuration 어노테이션과 충돌하게 되어, 중복된 설정이 발생할 수 있다.
그러므로 Async 설정 클래스를 만들어서 @EnableAsync를 붙여주는 것이 권장된다.
@Configuration
@EnableAsync // 비동기 어노테이션을 사용할 수 있게 함
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
// ThreadPoolTaskExecutor를 사용하여 비동기 작업을 처리할 Executor를 설정
ThreadPoolTaskExecutor** executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 기본 스레드 풀 크기
executor.setMaxPoolSize(10); // 최대 스레드 풀 크기
executor.setQueueCapacity(25); // 큐의 최대 용량
executor.setThreadNamePrefix("Async-"); // 스레드 이름 접두사
executor.initialize(); // 초기화
return executor;
}
}
Executor는 Java 표준 라이브러리에서 제공하는 인터페이스로, 비동기 작업을 실행하는 데 사용된다.
🔸 AsyncConfigurer 인터페이스
AsyncConfigurer을 상속 받아 구현체를 구현하면, 해당 구현체에서 선언한 Executor을 사용한다.
또한 @Bean 을 사용하여 특정 Executor를 사용하도록 할 수 있다.
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Bean("CustomTaskExecutor")
public Executor CustomAsyncExecutor() {
// ThreadPoolTaskExecutor를 사용하여 비동기 작업을 처리할 Executor를 설정
ThreadPoolTaskExecutor** executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 기본 스레드 풀 크기
executor.setMaxPoolSize(10); // 최대 스레드 풀 크기
executor.setQueueCapacity(25); // 큐의 최대 용량
executor.setThreadNamePrefix("Coustom-"); // 스레드 이름 접두사
executor.initialize(); // 초기화
return executor;
}
}
@Async("CustomTaskExecutor")
public void testAsync() {
}
Custom Executor 구현해 빈으로 등록하고, @Async("빈 이름")를 사용하면 해당 Executor을 사용한다.
🔸 @Async와 Executor 설정
@Async 어노테이션을 사용할 때,
기본적으로 별도의 TaskExecutor를 설정을 해주지 않으면, SimpleAsyncTaskExecutor가 기본적으로 사용된다.
SimpleAsyncTaskExecutor는 스레드 풀을 사용하지 않고, 매 요청마다 새로운 스레드를 생성한다.
⇒ 리소스 부족으로 이어질 가능성이 높기 때문에 ThreadPoolTaskExecutor 와 같은 풀 기반의 TaskExecutor를 사용하는 것을 권장한다.
SpringBoot를 사용할 경우 autoConfiguration으로 ThreadPoolTaskExecutor가 자동으로 등록되기 때문에 application.yml으로 executor의 옵션을 지정해주는 방법도 있다.
spring:
task:
execution:
pool:
core-size: 5
max-size: 5
queue-capacity: 5
keep-alive: 30s
🔸 여러 Executor 빈 등록
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Bean
public Executor CustomAsyncExecutor1() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("Coustom1-");
executor.initialize();
return executor;
}
@Bean
public Executor CustomAsyncExecutor2() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("Coustom2-");
executor.initialize();
return executor;
}
}
만약 위 코드처럼 ThreadPoolTaskExecutor 2개가 빈으로 등록되어있다면, 이름이 taskExecutor인 빈을 우선적으로 사용한다. 만약 해당 빈이 없으면, SimpleAsyncTaskExecutor가 대신 사용된다.
🔹참고자료
https://swmobenz.tistory.com/41
'Spring' 카테고리의 다른 글
다이나믹 프록시(Dynamic Proxy)로 부가 기능 적용하기 (1) | 2025.03.16 |
---|---|
[Spring] @Async와 프록시 객체 (0) | 2025.03.12 |
멀티 Thread 테스트 : ExecutorService / CountDownLatch (1) | 2025.03.11 |
페이지네이션(Pagination) - 오프셋 기반, 커서 기반, Slice vs Page (0) | 2025.02.27 |
[SpringBoot] OpenAI API 적용하기 (0) | 2024.08.07 |