2022. 11. 21. 18:46ㆍBackEnd(Java)/Spring Boot
✅ 아래 내용들에 대해서 알아보자
- 비동기/동기, 블로킹/논블로킹 이해
- @Async 개념 및 사용법
- ThreadPoolExecutor 빈 등록 및 옵션 값 설정
@Aysnc를 이해하기 전에 먼저 비동기/동기, 블로킹/논블로킹 개념부터 이해를 해야한다..
혹시 헷갈리거나 잘 모르시는 분들은 아래 블로그 참고해주시면 됩니다.
https://inpa.tistory.com/entry/👩💻-동기비동기-블로킹논블로킹-개념-정리
@Async
스프링 부트에서 제공하는 손쉽게 사용할 수 있는 비동기 처리 방식
@Async 사용법
- @EnableAsync로 @Async를 사용하겟다고 선언한다
- 비동기로 수행되었으면 하는 메서드위에 @Async를 적용한다.
- 별도의 @Async 설정이 없으면 새로운 비동기 작업을 스레드 풀에서 처리하는 게 아니라 새로운 스레드를 매번 생성해서 작업을 수행시키는것이 디폴트 설정 ⇒ 이렇게 되면 많은 양의 스레드를 사용하게 되므로 비효율적이게 됨. 따라서 쓰레드 풀을 관리해줘야 한다
- 쓰레드 풀 관리를 위한 ThreadPoolTaskExecutor 빈 등록(옵션들 추가)
ThreadPoolTaskExecutor 빈 등록 예시 코드
package com.restudy.jpa_re;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "threadPoolTaskExecutor_test")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(1); //thread-pool 항상 살아 있는 thread 최소 개수
threadPoolTaskExecutor.setMaxPoolSize(10); //thread-pool에 사용할 수 있는 thread 최대 개수
threadPoolTaskExecutor.setQueueCapacity(2); //thread-pool에 사용할 최대 queue 크기
threadPoolTaskExecutor.setThreadNamePrefix("test threadPool"); //thread prefix
return threadPoolTaskExecutor;
}
}
비동기 로직을 가진 클래스
package com.restudy.jpa_re;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class AsyncTestService {
@Async("threadPoolTaskExecutor_test")
public void asyncHello(int i) {
log.info("async i ={}", i);
}
}
테스트 코드
@DisplayName("asyncTest")
@Test
public void asyncTest() throws Exception {
//given
System.out.println("logic start");
for(int i=0;i<100;i++){
asyncTestService.asyncHello(i);
}
//when
System.out.println("logic end");
//then
}
ThreadPoolTaskExecutor 옵션 값 정리
- corePoolSize : thread-pool에 항상 살아있는 thread 최소 개수
- maxPoolSize : thread-pool에서 사용할 수 있는 thread 최대 개수
- queueCapacity : thread-pool에서 사용할 최대 queue의 크기
- ThreadNamePrerfix : thread 이름의 prefix
- keepAliveSeconds : maxPoolSize가 모두 사용되다가 idel(유휴상태)로 돌아갔을 떄 종료하기까지 대기하는 걸리는 시간
- WaitForTasksToCompleteOnShutDown : 시스템 종료 시 queue에 남아있는 작업을 모두 완료한 후 종료 하도록 처리
- TaskDecorator : thread-pool로 작업을 시작하기 직전에 진행하는 작업을 추가할 수 있도록 하는 interface이다. 해당 interface를 구현하여 traceid를 복사하는 등의 작업을 할 수 있다(데코레이터 패턴)
- RejectedExecutionHandler : RejectedExecutionHandler는 ThreadPoolExecutor에서 task를 더 이상 받을 수 없을 때 호출된다. 이런 경우는 queue 허용치를 초과하거나 Executor가 종료되어 Thread 또는 큐를 사용할 수 없는 경우에 발생하게 된다.
@Async 튜닝 없이 사용 시 기본 값
- corePoolSize : 1
- maxPoolSize : Integer.MAX_VALUE
- keepAliveSeconds : 60(second)
- QueueCapacity : Integer.MAX_VALUE
- AllowCoreThreadTimeOut : false
- WaitForTasksToCompleteOnShutdown : false
- RejectedExecutionHandler : AbortPolicy
⇒기본값을 보았을 때, corePoolSize가 1이기 때문에 1개의 스레드로 들어오는 모든 일을 처리다.
이때문에 요청이 많이 들어오게 되면 처리의 지연이 발생할 수 있습니다.
그리고 더더욱 많은 요청이 들어와서 처리할 수 없는 수준이 된다면 RejectedExecutionHandler 값이 AbortPolicy이기 때문에 Exception을 발생시키며 종료되게 됩니다.
for문 테스트 리뷰
- @Async 튜닝 없이 사용하더라도 싱글 스레드로 도는것이 아니라, 멀티 스레드로 동작함
- 스레드 개수를 2개로 설정 후 동작 시 1부터 100까지 순차적으로 실행 됨
@Async 사용시 주의사항
- private 메서드에는 적용이 안된다. public만 가능
- self-invocation(자가 호출)해서는 안된다 → 같은 클래스 내부의 메서드를 호출해서는 안됨(다른 클래스의 메서드 호출)
번외)
Serial(직렬) vs Concurrent(동시)
이 두 개념은 큐의 특성에 대한 개념입니다.
- Serial
-> 시리얼큐는 큐가 받아들인 작업들을 하나의 스레드로만 보내 처리하는 큐입니다. 시리얼큐는 순서가 중요한 작업 을 처리할 때 사용
- Concurrent
-> 컨커런트큐는 받아들인 작업을 여러 개의 스레드로 나눠서 보내 처리하는 큐입니다. 이 때 몇 개의 쓰레드로 분산할지는 위에서 언급했듯시스템(OS)이 알아서 결정합니다. 컨커런트큐는 각자 중요도나 작업 성격이 독립적이지만 유사한 여러 개의 작업을 처리할 때 사용합니다. (예를 들면 테이블 뷰 각 셀에 들어가는 이미지나 텍스트 데이터를 API에서 가지고 올 때가 있습니다.)
스레드의 작업 처리 방식과 큐의 작업 분산 방식 조합
Sync(동기) Async(비동기)
Serial(직렬) | 작업을 다른 스레드에서 하도록 분산처리한 후 그 작업이 끝나길 기다렸다가 다음 작업을 큐에 넣습니다.이 때 다른 스레드에 작업을 분산하는 큐의 특성은 Serial입니다. 즉, 작업을 시키는 스레드(A)에서 분산처리 시킨 작업을 해당 스레드(A)가 아닌 다른 한 개의 스레드(B)에서 처리하도록 합니다(분배합니다.) | 작업을 다른 스레드에서 하도록 분산처리한 후 그 작업이 끝나길 기다리지 않고 다음 작업을 큐에 넣습니다.이 때 작업을 다른 스레드에 분산하는 큐의 특성은 Serial입니다. 즉, 작업을 시키는 스레드(A)에서 분산처리 시킨 작업을 해당 스레드(A)가 아닌 다른 한 개의 스레드(B)에서 처리하도록 합니다(분배합니다.) |
Concurrent(동시) | 작업을 다른 스레드에서 하도록 분산처리한 후 그 작업이 끝나길 기다렸다가 다음 작업을 큐에 넣습니다.이 때 작업을 다른 스레드에 작업을 분산하는 큐의 특성은 Concurrent 입니다. 즉, 작업을 시키는 스레드(A)에서 분산처리 시킨 작업을 해당 스레드(A)가 아닌 다른 여러 개의 쓰레드(B,C,D…)에서 처리하도록 합니다(분배합니다.) | 작업을 다른 쓰레드에서 하도록 분산처리한 후 그 작업이 끝나길 기다리지 않고 다음 작업을 큐에 넣습니다.이 때 다른 스레드에 작업을 분산하는 큐의 특성은 Concurrent입니다. 즉, 작업을 시키는 스레드(A)에서 분산처리 시킨 작업을 해당 스레드(A)가 아닌 다른 여러 개의 쓰레드(B,C,D…)에서 처리하도록 합니다.(분배합니다.) |
정리
동기/비동기 개념은 작업을 보내는 시점에서 작업이 끝날때 까지 기다릴지 말지를 결정하는 것
직렬/동시 개념은 큐로 보낸 작업들이 여러 개의 스레드로 갈지 아니면 하나의 스레드로 처리할지 결정하는 것
참고자료
'BackEnd(Java) > Spring Boot' 카테고리의 다른 글
스프링 이벤트 처리 (0) | 2023.01.20 |
---|---|
Doesn't say anything about org.gradle.plugin.api-version (required '7.6') (0) | 2023.01.03 |
javax.Transactional vs spring.Transactional (0) | 2022.09.17 |
@RequestBody, @RequestParam, @ModelAttribute (0) | 2022.06.26 |
org.springframework.web.multipart.support.missingservletrequestpartexception required request part 오류 (0) | 2022.06.22 |