본문으로 바로가기

우리가 Spring에서 @Async@EnableAsync 어노테이션을 통해

간단하게 메소드가 비동기로 동작하도록 설정할 수 있다.

 

하지만 비동기에 관한 설정을 커스텀하기가 어렵다.

기본적으로 @Async어노테이션은 SimpleAsyncUncaughtExceptionHandler를 상속받고 있기 때문이다.

 

🔽 SimpleAsyncUncaughtExceptionHandler를 사용하는 자세한 코드

AsyncConfig

때문에 AsyncConfigurerSupport를 상속받는 클래스를 생성하여 커스텀해보자.

주의!

AsyncCongifurer를 상속받아야 할 까, AsyncConfigurerSupport를 상속받아야 할까?

-> 둘 다 가능. 하지만 AsyncConfigurerSupport(클래스)의 상위 인터페이스가 AsyncConfigurer(인터페이스)이다.

때문에 클래스를 상속받는 것보다 인터페이스를 상속받는 것이 더 좋다고 생각하여 

AsyncConfigurer로 수정하는 방향으로 해야겠다.

 

수정 전 : extends AsyncCongifurerSupport

수정 후 : implements AsyncCongifurer

 

여기서는 AsyncConfig라고 하겠다.

이렇게 쓰레드를 어떻게 사용할 것인지 커스텀할 수 있다.

@EnableAsync : 비동기 활성화
@Configuration : 클래스를 빈으로 등록
@ThreadPoolTaskExecutor : 쓰레드 실행을 돕는 녀석
corePoolSize : 기본 실행대기하는 쓰레드 개수
maxPoolSize : corePoolSize와 QueueCapacity가 다 찼을 때 최대 실행 대기할 수 있는 쓰레드 개수
threadNamePrefix : 쓰레드이름 접두사

 

 

ThreadPoolTaskExecutor 기본적인 동작원리

corePoolSizequeueCapacity  maxPoolSize  queueCapacity  RejectedExecutionException

 

1. 현재 점유하고 있는 쓰레드 개수가 corePoolSize 만큼 있을 때 요청이 오면 queueCapacity의 개수만큼 요청을 큐에 넣는다.

2. 현재 점유하고 있는 쓰레드 개수가 corePoolSize 만큼 있고 queueCapacity개수만큼 큐에 요청이 차있을 때 

요청이 오면 maxPoolSize만큼 쓰레드풀을 생성한다.

3. 현재 점유하고 있는 쓰레드 개수가 maxPoolSize만큼 있고 queueCapacity 개수만큼 큐에 요청이 차있을 때

요청이 오면 RejectedExecutionException 예외가 발생한다.

 

때문에 이 경우를 대비해 방어로직을 추가해야 한다.

 

 

실험

corePoolSize를 1, maxPoolSize를 1, queueCapacity를 0으로 설정하고

@Async 설정된 메소드에 Thread.sleep(3)을 각각 추가하니 RejectedExecutionException 리젝티드엑세큐션익셉션 이 발생하였다.

Rejected Executor Exception :

비동기 요청이 너무 많아서 해당 요청을 처리하는 쓰레드가 쓰레드풀에 들어가지 못하여 발생한 예외 

해당 방어로직은 @Async 설정된 메소드를 사용하는 서비스 단에서 try catch로 해주었다.

 

방어로직은 아래와 같이 해주었다.

 

결과

비동기 요청이 정상적으로 처리가 되었다. corePoolSize로 설정해준 쓰레드풀 사이즈 5 만큼 쓰레드가 생성되어 처리가 되고 있다. 추가 요청은 Queue에 쌓이게 된다.

 

비동기 요청이 많아지면 RejecteExecutor 익셉션 발생하고

이에 대한 방어로직이 정상적으로 동작했다.