자바 thread pool
Thread Pool은 미리 생성된 여러 스레드들이 모여 작업을 처리하는 스레드 그룹입니다. 주로 서버나 애플리케이션에서 반복적인 작업이 필요할 때, 작업마다 새로운 스레드를 생성하고 종료하는 대신, 미리 준비된 스레드를 사용하여 효율적으로 작업을 처리합니다. 이렇게 하면 스레드 생성과 소멸에 필요한 시간과 리소스를 절약할 수 있습니다.
- 효율적인 리소스 관리:
- 스레드를 매번 생성하고 삭제하는 비용을 줄여 CPU와 메모리 사용을 최적화합니다.
- 일정 수의 스레드만 사용하여 리소스를 제한하기 때문에 시스템 과부하를 예방합니다.
- 성능 향상:
- 새로운 작업이 들어오면 미리 준비된 스레드를 활용하여 빠르게 작업을 처리합니다.
- 특히 웹 서버와 같이 다수의 요청을 동시에 처리해야 하는 경우, Thread Pool이 성능을 크게 향상시킵니다.
- 작업 대기열 관리:
- 스레드 수가 제한되어 있어, 할당된 스레드가 모두 바쁘다면 새로 들어오는 작업은 대기열에 보관됩니다.
- 스레드가 완료되면 다음 작업을 수행하므로 병목을 방지합니다.
적합한 상황
- 동시성 요구가 큰 작업: 웹 서버, 데이터베이스 연결, 대규모 병렬 계산 작업에서 효과적입니다.
- 고정된 수의 작업 처리: 주기적으로 발생하는 작업(예: 주기적인 데이터 동기화)에서 적절합니다.
- 비동기, 스케줄
자바 예시
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executorService = Executors.newFixedThreadPool(10); // 고정 크기 10개 스레드 풀 생성
for (int i = 0; i < 20; i++) {
executorService.submit(() -> {
System.out.println("Thread: " + Thread.currentThread().getName());
});
}
executorService.shutdown(); // 작업 종료 후 모든 스레드 종료
- newFixedThreadPool(int n): 고정된 개수의 스레드를 가진 스레드 풀 생성.
- newCachedThreadPool(): 필요할 때 스레드를 생성하고, 일정 시간 후 소멸하는 스레드 풀.
- newSingleThreadExecutor(): 하나의 스레드로 순차적으로 작업을 처리하는 스레드 풀.
- newScheduledThreadPool(int n): 일정 시간 간격으로 작업을 처리할 수 있는 스레드 풀.
spring 과 연관성
ExecutorService executorService = Executors.newFixedThreadPool(10);을 사용하면, Spring에서 이미 설정된 스레드 풀과는 별개로 고유한 스레드 풀을 10개의 스레드로 생성하게 됩니다. 즉, 이 방식으로 생성한 스레드 풀은 Spring의 관리 범위를 벗어나 독립적으로 작동하며, Spring의 설정과는 상관없이 고정된 10개의 스레드만을 사용하게 됩니다.
Spring에서 스레드 풀을 사용하는 권장 방식
Spring에서 스레드 풀을 사용할 때는 @Configuration과 ThreadPoolTaskExecutor를 활용하여 스레드 풀을 Bean으로 정의하는 것이 좋습니다. 이 방법으로 스레드 풀을 설정하면, Spring의 관리 하에 들어가고, 스레드 풀 설정을 쉽게 조정할 수 있습니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class ThreadPoolConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 기본적으로 유지할 스레드 수
executor.setMaxPoolSize(20); // 최대 스레드 수
executor.setQueueCapacity(500); // 대기열에 쌓일 작업 수
executor.setThreadNamePrefix("MyAppThread-"); // 스레드 이름 접두사 설정
executor.initialize();
return executor;
}
}
///
@Service
public class MyAsyncService {
@Async
// @Async("customTaskExecutor") 특정 빈을 사용할 경우 명시 가능
public void executeAsyncTask() {
System.out.println("Thread: " + Thread.currentThread().getName());
// 비동기 작업 수행
}
}
이렇게 ThreadPoolTaskExecutor로 스레드 풀을 설정하면, Spring에서 생성한 스레드 풀을 Bean으로 주입받아 사용 가능하며, Spring의 설정에 따라 스레드 풀이 관리됩니다.
ExecutorService와 ThreadPoolTaskExecutor 차이점
- 관리 범위: ExecutorService를 직접 생성하면 Spring 컨텍스트와 별도로 동작하지만, ThreadPoolTaskExecutor로 생성한 스레드 풀은 Spring의 관리 하에 들어갑니다.
- 설정 및 모니터링: Spring의 ThreadPoolTaskExecutor는 Spring Actuator와 통합되어 모니터링과 설정 관리가 용이합니다.
기본 스프링 thread pool 설정(빈으로 안하고 이렇게 해도 됨)
# Core pool size (기본 스레드 수)
spring.task.execution.pool.core-size=10
# Maximum pool size (최대 스레드 수)
spring.task.execution.pool.max-size=20
# Queue capacity (작업 대기열 크기)
spring.task.execution.pool.queue-capacity=500
# Thread name prefix (스레드 이름 접두사)
spring.task.execution.thread-name-prefix=MyAppExecutor-
Spring Boot의 기본 값
Spring Boot에서는 spring.task.execution.pool 설정을 명시하지 않으면, 사용되는 기본 값
- core-size (기본 스레드 수): 기본적으로 1로 설정됩니다.
- max-size (최대 스레드 수): 기본적으로 2147483647로 설정됩니다 (즉, 거의 무제한).
- queue-capacity (작업 대기열 크기): 기본적으로 2147483647로 설정됩니다.
- thread-name-prefix (스레드 이름 접두사): 기본적으로 SimpleAsyncTaskExecutor-로 설정됩니다.
Spring Boot의 기본 스레드 풀은 SimpleAsyncTaskExecutor를 사용하여 비동기 작업을 처리하는데, 이 Executor는 스레드 풀을 사용하지 않고 매번 새로운 스레드를 생성하므로 성능이나 리소스 관점에서 비효율적일 수 있습니다. 따라서, 실제 운영 환경에서는 ThreadPoolTaskExecutor를 설정하는 것이 좋습니다.
예시
ThreadPoolTaskExecutor 설정에 코어 사이즈 10으로 되어 있는데 @async에서 스래드 풀 8개 쓰고 1초 뒤에 @scheduled 에서 스래드 풀 5개 쓴다고 할 때의 상황
설정된 내용
- core-size: 10
- max-size: 20
- @Async에서 8개 스레드를 사용 중
동작 방식
- core-size가 10으로 설정되어 있기 때문에, 풀의 최소 크기는 10입니다. 만약 10개가 다 사용되면, 추가로 최대 20개까지 확장 가능합니다.
- 현재 @Async에서 8개 스레드를 사용하고 있다면, 2개 스레드는 유휴 상태로 남아 있습니다.
- @Scheduled가 5개 스레드를 요구할 때, 이미 8개의 스레드가 사용 중이므로, 추가로 필요한 5개를 풀에서 할당하려고 합니다.
5개 스레드를 할당하는 경우
- 현재 2개의 유휴 스레드가 남아 있기 때문에, @Scheduled는 이 2개의 스레드를 사용할 수 있습니다.
- 최대 풀 사이즈인 20개까지 확장될 수 있기 때문에, 추가로 3개의 스레드를 더 생성하여, 총 5개 스레드를 사용하게 됩니다.
따라서, 3개의 스레드가 추가로 생성되고, 총 5개의 스레드가 사용됩니다. 작업이 끝난 후 스레드 수는 다시 줄어들어 설정된 core-size인 10개로 돌아갑니다.
만약 두 작업이 서로 다른 스레드 풀을 사용하도록 분리하고 싶다면, @Async와 @Scheduled에 각각 다른 ThreadPoolTaskExecutor를 지정할 수도 있습니다. 이 경우 두 작업은 스레드 풀이 충돌되지 않고 완전히 독립적으로 실행됩니다.
스프링의 thread pool은 async 나 scheduled에서 사용
Spring Boot는 자체적으로 필요한 기본 설정을 가지고 있어, 특별히 스레드 풀을 명시적으로 설정하지 않으면 자동으로 기본 스레드 풀을 생성하고 이를 사용합니다. 이 기본 스레드 풀은 Spring이 비동기 작업(@Async)이나 스케줄링 작업(@Scheduled) 등을 처리할 때 사용됩니다.
일반적인 API요청 시
일반 동기 API 요청에서는 특별히 스레드 풀을 직접 관리하지 않습니다. 대신, Spring MVC(또는 Spring WebFlux와 같은 비동기 모델)를 사용할 경우 서버 컨테이너(예: Tomcat, Jetty, Undertow)가 자동으로 요청을 처리하기 위한 스레드 풀을 관리합니다.
동기 요청 시 스레드 풀 동작 방식
- Spring MVC에서 동기 API 요청이 들어오면, 서버 컨테이너가 요청마다 스레드를 할당합니다.
- Tomcat, Jetty, Undertow와 같은 서버들은 기본적으로 내부에 스레드 풀을 관리하며, 들어오는 요청을 처리하기 위해 해당 스레드 풀의 스레드를 사용합니다.
- 서버 컨테이너는 일반적으로 최소 스레드 수, 최대 스레드 수, 대기 큐 크기 등의 설정을 제공하여, 트래픽에 맞춰 성능을 조절할 수 있습니다.
application.properties 설정
# 최소 스레드 수 (초기화 시 생성할 스레드 수)
server.tomcat.min-threads=10
# 최대 스레드 수 (요청이 많을 때 생성 가능한 최대 스레드 수)
server.tomcat.max-threads=200
# 요청을 대기열에 얼마나 보관할 수 있는지 설정 (큐의 크기)
server.tomcat.accept-count=100
동기 API 요청의 경우, 하나의 요청에 대해 한 스레드가 전용으로 할당됩니다. 반면, 비동기 API 요청에서는 별도의 스레드 풀을 활용해 여러 작업을 동시에 처리할 수 있고, 비동기 작업이 완료될 때만 해당 스레드에 응답을 전송합니다.
thread pool 을 사용하지 않고 필요 시 thread 를 생성하고 종료하는 방식
- 비효율성: 매번 스레드를 새로 생성하고 소멸시키기 때문에 오버헤드가 큽니다. 단순 반복적인 작업에는 적합하지 않습니다.
- 적합한 상황: 자주 호출되지 않는 작업, 또는 실행 시간이 길고 독립적인 작업에 적합하며, 스레드 풀이 필요하지 않은 단순한 프로그램이나 이벤트 드리븐 방식에 유용합니다. 예를 들어, 특정 이벤트가 발생할 때마다 독립적인 작업을 수행하는 경우입니다.
- 리소스 관리: 스레드가 고정된 풀에 묶여 있지 않아 메모리와 CPU 부담이 증가할 수 있습니다.
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println("Thread: " + Thread.currentThread().getName());
}).start();
}
'개발 > java' 카테고리의 다른 글
DB로 분산락 구현 (2) | 2024.11.21 |
---|---|
[test] org.mockito.exceptions.misusing.PotentialStubbingProblem (1) | 2024.11.15 |
[test] object mother (2) | 2024.09.26 |
자바 버전별 특징 (0) | 2024.09.23 |
[java8+] 함수형 프로그래밍 @FunctionalInterface (0) | 2024.09.21 |