Backend/JAVA

스레드 풀(Thread Pool), Executor

dddzr 2024. 6. 5. 15:33

스레드 풀(Thread Pool)

스레드 풀은 일정 수의 스레드를 미리 생성하여 관리하는 기법입니다.

작업 큐에  새로운 작업이 들어오면, 스레드 풀에서 사용 가능한 스레드가 해당 작업을 처리하고

작업이 완료되면, 해당 스레드는 다음 작업을 위해 다시 풀에 반환됩니다.

 

사용 이유

  • 자원 관리: 스레드 생성과 소멸의 오버헤드를 줄이고, 자원을 효율적으로 관리할 수 있습니다.
  • 제한된 스레드 수: 시스템에서 생성할 수 있는 스레드 수를 제한함으로써 자원 고갈 및 성능 저하를 방지할 수 있습니다.
  • 응답성 및 처리량 향상: 미리 생성된 스레드를 재사용(작업을 대기상태로 유지)함으로써 성능을 향상시킬 수 있고, 작업이 발생하면 대기 중인 쓰레드 중 하나를 선택하여 작업을 할당하므로, 작업 처리를 병렬로 진행할 수 있습니다.

 

Executor

 Executor는 Java에서 스레드를 실행하는 방법을 추상화한 인터페이스입니다. Executor를 사용하면 스레드를 직접 생성하고 관리하는 대신, 작업을 Executor에 제출하면 됩니다. ExecutorService는 Executor의 하위 인터페이스로, 스레드 풀을 관리하고 제어할 수 있는 기능을 제공합니다.

 

주요 메서드

  • execute(Runnable command): 주어진 Runnable 작업을 실행합니다.
  • submit(Callable<T> task) / submit(Runnable task) : 주어진 Callable / Runnable  작업을 실행하고, Future 객체를 반환합니다.
  • shutdown(): 더 이상 새로운 작업을 받지 않고, 현재 실행 중인 작업들을 완료한 후 종료합니다.
  • shutdownNow(): 현재 실행 중인 작업들을 중지하고, 스레드 풀을 즉시 종료합니다.

 

스레드 풀의 유형

Java에서 제공하는 다양한 유형의 스레드 풀이 있습니다:

  1. FixedThreadPool
    • 고정된 수의 스레드를 유지하는 스레드 풀입니다. 초과된 작업은 내부 큐에서 대기합니다.
    • 예시: Executors.newFixedThreadPool(3);
  2. CachedThreadPool
    • 필요할 때 스레드를 생성하고, 일정 시간 동안 사용되지 않은 스레드를 제거합니다.
    • 예시: Executors.newCachedThreadPool();
  3. SingleThreadExecutor
    • 단일 스레드로 작업을 처리합니다. 순차적으로 작업을 처리하고, 스레드가 종료되지 않도록 유지합니다.
    • 예시: Executors.newSingleThreadExecutor();
  4. ScheduledThreadPool
    • 일정 시간 후 또는 주기적으로 작업을 실행합니다.
    • 예시: Executors.newScheduledThreadPool(3);

 

*참고: 스레드가 수행할 작업을 정의 (Callable, Runnable)

https://sumni.tistory.com/338

 

Callable, Runnable, Future

Runnable과 Callable은 둘 다 자바에서 멀티스레딩을 구현할 때 사용되는 인터페이스입니다.두 인터페이스는 스레드가 수행할 작업을 정의하며, ExecutorService와 같은 스레드 풀에서 관리되고 실행될

sumni.tistory.com

 

예제 1: 기본적인 스레드 풀 생성 및 사용

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 고정된 크기의 스레드 풀 생성
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // Runnable 작업 정의
        Runnable task = () -> {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " is executing task.");
        };

        // 작업을 스레드 풀에 제출
        for (int i = 0; i < 10; i++) {
            executorService.execute(task);
        }

        // 스레드 풀 종료
        executorService.shutdown();
    }
}

 

위 예제는 3개의 스레드를 가진 스레드 풀을 생성하고, 10개의 Runnable 작업을 제출하여 실행하는 간단한 예제입니다. executorService.shutdown()을 호출하여 더 이상 새로운 작업을 받지 않도록 하고, 현재 실행 중인 작업들을 완료한 후 종료합니다.

 

예제 2: Callable과 Future를 사용한 예제

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableExample {
    public static void main(String[] args) {
        // 고정된 크기의 스레드 풀 생성
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // Callable 작업 정의
        Callable<String> task = () -> {
            String threadName = Thread.currentThread().getName();
            return threadName + " is executing task.";
        };

        // 작업을 스레드 풀에 제출하고 Future 객체 반환받기
        Future<String> future = executorService.submit(task);

        try {
            // Future 객체로부터 결과 가져오기
            String result = future.get();
            System.out.println(result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        // 스레드 풀 종료
        executorService.shutdown();
    }
}

위 예제는 Callable 인터페이스를 사용하여 작업을 정의하고, 해당 작업을 스레드 풀에 제출하여 Future 객체를 반환받습니다. future.get()을 호출하여 작업의 결과를 가져옵니다.