Spring Boot에서 ExecutorService로 안정적인 멀티스레딩 구현하기

궁금하면 500원·2025년 4월 30일
0

미생의 스프링

목록 보기
39/43

Spring Boot에서 ExecutorService를 활용한 멀티스레딩 도입

Spring Boot 프로젝트에 멀티스레딩을 도입할 때 ExecutorService를 활용하는 이유, 적용 방식, 그리고 시스템 자원을 효과적으로 사용하는 방법까지 정리해보았습니다.

1. 왜 멀티스레딩인가?

대규모 요청 처리, 비동기 작업, 외부 API 병렬 호출, 파일 처리 등 I/O 중심 작업에서 성능 병목을 피하려면 멀티스레딩이 필요합니다.

Spring의 기본 서블릿 기반 요청 처리 방식은 각 요청당 하나의 스레드를 사용하기 때문에, 복잡한 비동기 처리가 필요한 경우 직접 스레드 관리를 할 수 있는 구조가 필요합니다.

2. ExecutorService란?

ExecutorService는 Java의 java.util.concurrent 패키지에 포함된 인터페이스로, 스레드 풀을 구성하고 효율적인 스레드 실행을 관리해줍니다.
직접 스레드를 생성/제어하는 방식보다 안전하고, 유지보수가 쉽습니다.

주요 특징

  • 스레드 풀 기반으로 자원 낭비 방지
  • 작업 큐 관리
  • graceful shutdown 가능

3. Spring Boot에서 ExecutorService 적용 방법

(1) Bean 등록

@Configuration
public class ExecutorConfig {

    @Bean
    public ExecutorService taskExecutor() {
        return Executors.newFixedThreadPool(10); // 동시에 최대 10개의 작업 처리
    }
}

(2) 사용 예시

@Service
public class ReportService {

    private final ExecutorService executorService;

    public ReportService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public void generateReports(List<Long> reportIds) {
        for (Long id : reportIds) {
            executorService.submit(() -> {
                // 실제 보고서 생성 로직
                processReport(id);
            });
        }
    }

    private void processReport(Long id) {
        // time-consuming logic
        try {
            Thread.sleep(3000);
            System.out.println("Report created: " + id);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

4. 시스템 자원을 효율적으로 사용하는 방법

(1) 스레드 수 조절

  • CPU 바운드 작업: 스레드 수 = CPU 코어 수
  • I/O 바운드 작업: 스레드 수 = CPU 코어 수 * 2 ~ 4

(2) ThreadPoolExecutor 사용

좀 더 정밀한 제어를 위해 아래처럼 사용 가능 합니다.

@Bean
public ExecutorService customThreadPool() {
    return new ThreadPoolExecutor(
        5, // core pool size
        10, // max pool size
        60L, TimeUnit.SECONDS, // idle thread keep-alive time
        new LinkedBlockingQueue<>(100), // queue size 제한
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy() // 큐가 가득 찼을 때 정책
    );
}

(3) Shutdown 처리

스프링 종료 시 Executor를 안전하게 종료해야 리소스 누수 방지 가능 합니다.

@PreDestroy
public void shutdown() {
    executorService.shutdown();
}

(4) 비동기 실행 결과 확인: Future

Future<String> result = executorService.submit(() -> "작업 완료");
String output = result.get();

5. 주의할 점

  • 너무 많은 스레드는 오히려 시스템을 느리게 한다. CPU 점유율, 메모리, GC 상태 등을 모니터링하면서 튜닝하는것 입니다.
  • 병렬로 실행되는 작업들이 공유 자원을 사용하는 경우, 반드시 동기화 고려 해야됩니다
  • 예외 처리 로직 빠짐없이 작성: submit() 내의 예외는 직접 캐치하지 않으면 무시됩니다.

6. 결론

ExecutorService를 활용한 멀티스레딩은 단순한 비동기 처리를 넘어서 시스템 전반의 처리 성능을 높일 수 있는 강력한 도구 입니다.

하지만 자원 관리, 예외 처리, 스레드 풀 설정을 신중히 해야 진짜 '성능 향상'으로 이어진다. 실무에서는 JVM 튜닝, 로깅, 모니터링과 함께 적용하는 것을 추천 합니다.

멀티스레딩은 도구일 뿐, 무작정 쓰면 독이 된다. 필요한 지점에서 정밀하게 활용하는 게 관건입니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글