스레드 풀은 시스템 자원을 효율적으로 사용하고, 안정적인 작업 처리를 위해 필수적인 요소다. 다양한 상황에 맞춰 적절한 풀 생성 전략과 작업 거절 정책을 설정하면, 트래픽 급증 시에도 시스템이 예측 가능한 성능을 유지할 수 있다. 아래는 대표적인 Executor 기반 스레드 풀 생성 전략과 거절 정책에 대한 정리다.
Executor 전략
Executor 스레드 풀 관리
- 스레드 관리 속성
- corePoolSize
기본적으로 유지되는 스레드 수다. 이 수만큼의 스레드를 초기화해두고, 작업이 들어오면 먼저 활용한다. - maximumPoolSize
생성될 수 있는 스레드의 최대치다.corePoolSize
이상의 스레드는 필요한 경우에만 만들고, 더 이상 새 스레드를 만들 수 없으면 작업을 거절한다. - keepAliveTime, timeUnit
corePoolSize
를 초과해 생성된 스레드(초과 스레드)가 대기할 수 있는 시간이다. 이 시간 동안 추가 작업이 없으면 초과 스레드를 제거한다. - BlockingQueue workQueue
작업이 들어오면 우선적으로 스레드를 할당받아 실행하지만, 스레드가 모두 바쁘면 이 큐에 작업을 쌓아둔다.
- corePoolSize
- 초과 스레드의 생성 시점
더 이상 사용 가능한 스레드가 없고, 큐에도 작업이 들어갈 공간이 없을 때 초과 스레드가 생성된다. 단, 이는maximumPoolSize
에 도달하기 전까지 가능하다. "초과 스레드"란, 정말 어쩔 수 없이 생성되는 스레드를 의미한다. - 초과 스레드 생성이 불가능한 상황
이미maximumPoolSize
에 도달했음에도 불구하고, 큐가 가득 차 있으면 더 이상 스레드를 생성하지 못하고 작업을 거절하게 된다. - 스레드를 미리 생성하는 경우
서버 응답 시간이 중요한 상황에서는 스레드를 미리 생성해두고 싶을 수 있다. 이때는ThreadPoolExecutor
의prestartAllCoreThreads()
메서드를 호출해 모든corePoolSize
스레드를 미리 시작할 수 있다. 단,ExecutorService
인터페이스에는 해당 메서드가 없으므로,ThreadPoolExecutor
로 다운캐스팅해야 한다.
Executor 전략 - 단일 풀 전략
newSingleThreadExecutor()
- 기본 스레드를 1개만 사용하는 풀이다.
- 큐에는 제한이 없으며, 들어온 작업을 순차적으로 처리한다.
- 주로 간단한 테스트나 순차적 처리가 필요한 경우에 사용된다.
Executor 전략 - 고정 풀 전략
newFixedThreadPool(nThreads)
- 일반적인 상황에서 많이 사용되며, 트래픽이 비교적 예측 가능한 경우에 적합하다.
- 스레드 풀에
nThreads
개의 스레드를 고정적으로 생성한다. - 큐로는 무제한 크기의
LinkedBlockingQueue
를 사용해, 작업이 들어오면 스레드가 사용 가능할 때까지 대기시킨다. - 스레드 수가 고정되어 있어 CPU 및 메모리 사용량을 예측 가능하게 유지하지만, 트래픽이 점차 증가하거나 실시간 이벤트로 급증하는 경우 병목이 발생할 수 있다.
- CPU/메모리 리소스는 여유로운데, 사용자는 느려지는 문제가 발생하는 것이다.
- 언젠가 처리되겠지만, 언제 처리될지는 모르는 불쾌한 UX를 사용자에게 경험시키게 된다.
Executor 전략 - 캐시 풀 전략
newCachedThreadPool()
- 기본 스레드를 0개로 시작하며, 필요 시 즉시 스레드를 생성한다.
- 큐로
SynchronousQueue
를 사용하여 작업이 들어오면 즉시 처리할 수 있는 스레드를 할당받는다. - 트래픽이 증가하면 빠르게 스레드를 늘릴 수 있으며, 일정 시간(
keepAliveTime
) 동안 추가 작업이 없으면 생성된 스레드를 정리한다. - 모든 작업이 대기 없이 바로 실행되지만, 트래픽 폭주 시 메모리 사용량이 급증할 수 있는 위험이 있다.
- CPU/메모리 리소스가 매우 부족해지고,
- 병목 현상이 발생함으로써, 살짝의 변화에도 매우 민감하게 반응하게 된다.
- 컨텍스트 스위칭 비용이 지나치게 높아지고
- 메모리 제한을 넘어갈 경우, 서비스가 다운되는 문제가 발생할 수 있다.
Executor 전략 - 사용자 정의 풀 전략
직접 ThreadPoolExecutor
객체를 생성하여 설정한다.
- 예를 들어, 1100개의 작업은
corePoolSize
로 처리하고, 그 이후 100개의 작업은 초과 스레드를 생성하여 처리하도록 설정할 수 있다. - 큐의 자료구조와
RejectedExecutionHandler
구현 방식을 개발자가 직접 정의할 수 있어, 트래픽 변화에 유연하게 대응 가능하다. - 이는 트래픽이 점차 증가하거나 실시간 이벤트로 폭주하는 상황에서 유용하다.
Executor 예외 정책 - 작업 거절 정책
스레드 풀이 처리할 수 있는 한계를 넘어서는 작업이 들어오면, 아래와 같은 작업 거절 정책이 적용된다. 기본 정책 외에도 개발자가 원하는 방식으로 사용자 정의 거절 정책을 구현할 수 있다.
AbortPolicy
- 정책 개요
기본 설정 정책이며, 작업 거절 시RejectedExecutionException
을 발생시킨다. - 설정 방식
- 처리 방식
- 클래스 설명
AbortPolicy
는RejectedExecutionHandler
인터페이스를 구현한 대표적인 정책으로, 스레드 풀이 처리할 수 없는 작업이 들어올 경우 예외를 던진다.
DiscardPolicy
- 정책 개요
새로운 작업을 아무런 예외 없이 조용히 버린다. - 설정 방식
- 처리 방식
추가 조치 없이 작업을 무시한다. - 클래스 설명
DiscardPolicy
는 별도의 로직 없이, 작업을 버리는 방식으로 처리한다.
CallerRunsPolicy
- 정책 개요
작업을 넘기려던 생산자 스레드가 직접 해당 작업을 수행하게 한다. - 설정 방식
- 처리 방식
이 방식은 생산자 스레드가 작업을 처리함으로써, 전체적인 생산 속도를 늦추어 과부하를 방지하는 효과가 있다.
- 클래스 설명
CallerRunsPolicy
는 내부적으로run()
메서드를 직접 호출하는 방식으로 작업을 처리한다.
사용자 정의 (RejectedExecutionHandler)
- 정책 개요
개발자가 직접 원하는 방식으로 작업 거절 정책을 정의할 수 있다. - 설정 방식
- 사용자 정의 구현
개발자가 필요에 따라 로그 기록, 모니터링, 특정 알림 전송 등의 추가 로직을 포함할 수 있다.
- 실제 출력된 결과값
스레드 풀 구성과 작업 거절 정책 설정은 애플리케이션의 성능과 안정성에 큰 영향을 미친다. 트래픽 상황과 시스템 자원 특성을 면밀히 분석해, 필요 이상으로 스레드를 남발하지 않으면서도 적절한 처리량을 유지할 수 있는 구성을 선택해야 한다. 이러한 결정이 안정적인 시스템 운영의 핵심이 된다.
출처: 김영한 Java 고급 - 멀티쓰레드와 동시성
'WEB BE > JAVA' 카테고리의 다른 글
Java 의 주요 자료구조 - ArrayList & Hash Map (0) | 2025.03.09 |
---|---|
Java 에 대하여 (0) | 2025.03.07 |
Java Thread Model 의 역사 (0) | 2025.01.06 |
[Test] LocalDateTime 을 사용하는 함수의 테스팅 방법 (0) | 2024.10.13 |