Spring의 멀티스레드 관리를 위한 ThreadPoolExecutor를 알아보자

salgu·2024년 12월 2일
0

Spring

목록 보기
22/22
post-thumbnail

이미지 출처: https://rudaks.tistory.com/entry/자바의-쓰레드-풀-ExecutorService

ThreadPoolExecutor이란

ThreadPoolExecutor는 ExecutorService 인터페이스를 구현한 클래스이고, 스레드 풀을 관리하고 작업(Task)을 효율적으로 실행할 수 있도록 도와주는 도구입니다.

스레드 풀 관리란

스레드 풀이란, 일정 수의 스레드를 미리 생성해 두고, 필요할 때 작업을 할당하는 구조입니다.
스레드 풀이 없으면 작업마다 새로운 스레드를 생성하게 되어 성능 저하와 리소스 낭비가 발생할 수 있습니다.

작업 큐란

실행할 작업은 내부 작업 큐(버퍼)에 추가되고, 스레드 풀 내의 스레드(소비자)가 순차적으로 작업을 처리합니다.
작업의 순서를 조정하거나 처리량을 제한하는 데 유리합니다.

ThreadPoolExecutor 생성자

ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime,
                   TimeUnit unit,
                   BlockingQueue<Runnable> workQueue,
                   ThreadFactory threadFactory,
                   RejectedExecutionHandler handler)

ThreadPoolExecutor 생성자는 스레드 풀의 동작을 세부적으로 설정할 수 있도록 다양한 파라미터를 제공합니다.

corePoolSize (핵심 스레드 수)

스레드 풀에서 유지할 최소 스레드 수를 설정합니다.
초기에는 핵심 스레드만 생성되고, 작업량이 많아지면 최대 스레드 수(maximumPoolSize)까지 증가할 수 있습니다.
핵심 스레드는 allowCoreThreadTimeOut(true)를 설정하지 않는 한, 작업이 없어도 종료되지 않습니다.

maximumPoolSize (최대 스레드 수)

스레드 풀에서 허용할 수 있는 최대 스레드 수를 설정합니다.
작업 큐가 가득 찬 경우, 최대 스레드 수까지 스레드를 늘려 작업을 처리합니다.
workQueue가 무한 큐일 경우, 이 값은 사실상 무시됩니다.

keepAliveTime (유휴 스레드 유지 시간)

핵심 스레드를 제외한 추가 생성된 스레드가 유휴 상태일 때, 제거되기까지의 대기 시간을 설정합니다.
단위는 unit으로 설정됩니다.

unit (시간 단위)

keepAliveTime에 대한 시간 단위를 지정합니다.
예) TimeUnit.SECONDS, TimeUnit.MILLISECONDS.

workQueue (작업 큐)

대기 중인 작업을 저장하는 큐로, Runnable 객체를 담습니다.
예)

  • ArrayBlockingQueue(고정 크기 큐)
  • LinkedBlockingQueue(무제한 크기 큐)
  • SynchronousQueue(즉시 전달 큐)

threadFactory (스레드 생성기)

스레드를 생성할 때 사용할 팩토리로, 스레드 이름을 지정하거나 우선순위를 설정할 수 있습니다.
기본값은 Executors.defaultThreadFactory() 입니다.

handler (거부 정책)

작업 큐가 가득 차고, 스레드 풀의 최대 스레드 수를 초과했을 때 처리 방식을 지정합니다.

  • 기본 값 AbortPolicy(예외 발생)
  • CallerRunsPolicy(작업을 호출한 스레드에서 실행)
  • DiscardPolicy(작업 삭제)
  • DiscardOldestPolicy(가장 오래된 작업 삭제)
  • RejectedExecutionHandler 인터페이스를 직접 구현하여 커스텀

ThreadPoolExecutor 동작 방식

  1. 작업을 요청하면 corePoolSize 만큼 스레드를 만들어서 작업을 실행한다.
  2. corePoolSize를 초과하면 workQueue에 작업을 넣는다.
  3. workQueue의 작업량이 capacity로 정해놓은것보다 크다면 maximumPoolSize만큼 스레드를 만든다. 임시로 사용되는 초과 스레드가 생성된다.
    큐가 가득차서 큐에 넣을 수도 없다. 초과 스레드가 바로 수행해야 한다. (workQueue에 add하는것이 아님)
  4. workQueue가 꽉차고 maximumPoolSize를 초과하면 요청을 거절한다. 예외가 발생한다. (handler 동작)

ThreadPoolExecutor 전략

newSingleThreadPool(): 단일 스레드 풀전략

new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
                             new LinkedBlockingQueue<Runnable>())

스레드 풀에 기본 스레드 1개만 사용합니다.
LinkedBlockingQueue를 사용해 큐 사이즈에 제한이 없습니다.
간단히 사용하거나, 테스트 용도로 사용합니다.

newFixedThreadPool(nThreads) - 고정 스레드 풀 전략

 new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>())

스레드 풀에 nThreads 만큼의 기본 스레드를 생성하고 초과 스레드는 생성하지 않습니다.
LinkedBlockingQueue를 사용해 큐 사이즈에 제한이 없습니다.
스레드 수가 고정되어 있기 때문에 CPU, 메모리 리소스가 어느정도 예측 가능한 안정적인 방식입니다.

스레드풀 모니터링을 통해 workQueue에 병목이 있는지 확인을 해주는것이 좋습니다.

newCachedThreadPool() - 캐시 스레드 풀 전략

ThreadPoolExecutor es = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60,
TimeUnit.SECONDS, new SynchronousQueue<>());

기본 스레드를 사용하지 않고, 60초 생존 주기를 가진 초과 스레드만 사용합니다. 초과 스레드의 수는 제한이 없습니다.
SynchronousQueue를 사용하여 작업을 저장하지 않습니다.
대신에 생산자의 요청을 스레드 풀의 소비자 스레드가 직접 받아서 바로 처리한다. 모든 요청이 대기하지 않고 스레드가 바로바로 처리한다. 따라서 빠른 처리가 가능하다.

SynchronousQueueBlockingQueue 인터페이스의 구현체 중 하나이고 내부에 저장 공간이 없습니다.
생산자 스레드가 큐가 작업을 전달하면 소비자 스레드가 큐에서 작업을 꺼낼 때 까지 대기합니다.

기본 스레드도 없고, 대기 큐에 작업도 쌓이지 않습니다.
대신에 작업 요청이 오면 초과 스레드로 작업을 바로바로 처리하기 때문에 빠른 처리가 가능합니다.
초과 스레드의 수도 제한이 없기 때문에 CPU, 메모리 자원만 허용한다면 시스템의 자원을 최대로 사용할 수 있다.

작업 수에 맞추어 스레드 수가 변하기 때문에 어플리케이션의 메모리와 CPU를 잘 모니터링하여 스케일링을 잘해줘야됩니다.





Reference:

  • 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
profile
https://github.com/leeeesanggyu, leeeesanggyu@gmail.com

0개의 댓글