---
# System prepended metadata

title: Thread Pool
tags: [과외(하희영)]

---

## Thread Pool

![](https://hackmd.io/_uploads/HJa51FYu3.png)

- 쓰레드 풀(thread pool)은 병렬 컴퓨팅에 사용되는 소프트웨어 디자인 패턴이다.
  - 이 패턴은 프로그램이 프로그램의 수명 주기 동안 생성하고 소멸하는 쓰레드의 수를 미리 결정함으로써 일반적인 비용을 최소화하는 데 중점을 둔다 
- 쓰레드 풀은복잡한 작업을 여러 쓰레드로 분할하여 동시에 실행할 수 있으므로, 멀티코어 프로세서의 이점을 최대한 활용할 수 있다.
- 쓰레드 풀은 고성능 컴퓨팅, 웹 서버, 데이터베이스, 그래픽 렌더링 등 많은 컴퓨팅 작업에서 활용된다.
- 쓰레드 풀은 프로그램의 초기화 단계에서 생성되며, 이는 프로그램이 요구에 따라 쓰레드를 생성하거나 종료하는 데 드는 비용을 절감하고, 쓰레드의 최대 수를 미리 결정함으로써 시스템 자원의 과다한 사용을 방지한다.
- 쓰레드 풀에서는 일반적으로 다음과 같은 작업을 수행한다
  1. **쓰레드 생성**: 쓰레드 풀은 프로그램 시작 시에 일정 수의 쓰레드를 생성한다. 이렇게 미리 생성되는 쓰레드의 수는 풀의 최소 크기를 결정한다
  2. **작업 할당**: 쓰레드 풀은 프로그램에서 실행해야 하는 작업을 받아들이고, 이를 쓰레드에 할당한다. 일반적으로 이 작업은 큐(queue)나 스택(stack) 같은 자료구조를 통해 관리된다.
  3. **쓰레드 관리**: 쓰레드 풀은 풀의 현재 크기와 작업의 수를 기반으로 필요한 경우 추가적인 쓰레드를 생성하거나, 반대로 필요하지 않은 쓰레드를 종료할 수 있다. 이는 동적으로 시스템의 로드를 관리하는 데 도움이 된다.
  4. **쓰레드 재사용**: 쓰레드 풀에서는 일단 작업이 완료된 쓰레드를 종료하지 않고, 다음 작업에 재사용한다. 이는 쓰레드 생성과 소멸에 대한 비용을 크게 줄일 수 있다.


### 직접 만들어보는 Thread Pool

```java
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;

public class CustomThreadPool {
    private final List<Thread> threads = new ArrayList<>();
    private final Queue<Runnable> queue = new ConcurrentLinkedDeque<>();

    public CustomThreadPool(int poolSize) {
        for (int i = 0; i < poolSize; i++) {
            Thread thread = new Thread(() -> {
                while (true) {
                    try {
                        Runnable job = queue.poll();
                        if (job != null) {
                            job.run();
                        } else {
                            Thread.sleep(20);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
            threads.add(thread);
            thread.start();
        }
    }

    public void execute(Runnable task) {
        queue.add(task);
    }

    public static void main(String[] args) {
        CustomThreadPool pool = new CustomThreadPool(5);

        for (int i = 0; i < 10; i++) {
            int nowCount = i;
            pool.execute(() -> System.out.println("Task " + nowCount + " is running."));
        }
    }
}

```

## ExecutorService

![](https://blog.kakaocdn.net/dn/bcGfch/btrGEhU3Y4W/owqnKjYucjVNZDu4TwEdZ0/img.gif)

- 병렬 작업 시 여러개의 작업을 효율적으로 처리하기 위해 제공되는 자바 라이브러리
- 흔히 말하는 ThreadPool구현을 매우 용이하게 할 수 있으므로, Java에서 스레드 풀을 사용하고자 할 때 사용한다
- Runnable, Callable 중 하나를 상속하여 구현한 클래스를 인자로 받아서 처리할 수 있다.
  - Runnable : return 값이 없는 스레드
  - Callable : return 값이 있는 스레드

### ExecutorService의 종류

- `CachedThreadPool` : 스레드를 캐싱하는 스레드 풀. 필요할 때 필요한 만큼 스레드 풀을 생성한다. 이미 생성된 스레드를 재활용할 수 있기 때문에 성능상의 이점이 있을 수 있다. 다만 스레드 수가 폭발적으로 증가할 수 있다. (여기서 쓰이는 캐싱의 의미는 일정시간동안 스레드를 검사한다는 뜻이다. 60초동안 작업이 없으면 Pool에서 제거한다)
  - `Executors.newCachedThreadPool()`
- `ScheduledThreadPool` : 일정 시간 뒤에 실행되는 작업이나 주기적으로 수행되는 작업에 사용된다.
  - `Executors.newScheduledThreadPool()`
- `FixedThreadPool` : 고정된 개수를 가진 스레드풀
  - `Executors.newFixedThreadPool()`
- `SingleThreadExecutor` : 한 개의 스레드로 작업을 처리하는 스레드풀. 스레드 풀이라기보단 TaskPool의 개념이 더 적합하다
  - `Executors.newSingleThreadExecutor()`

#### 메서드

- `submit()` : 작업을 추가할 땐 `submit` 메서드를 사용하면 된다
- `shutdown()` : 더 이상 쓰레드풀에 작업을 추가하지 못하도록 한다. 그리고 처리 중인 Task가 모두 완료되면 쓰레드풀을 종료시킨다.
- `awaitTermination()` : 이미 수행 중인 Task가 지정된 시간동안 끝나기를 기다리고 지정된 시간 내에 끝나지 않으면 false를 리턴하며, 이 때 `shutdownNow()`를 호출하면 실행 중인 Task를 모두 강제로 종료시킬 수 있다.

#### ExecutorService 예시

**객체 생성**

```java
ExecutorService executor = Executors.newFixedThreadPool(4);
```

**작업 추가**

```java
ExecutorService executor = Executors.newSingleThreadExecutor();

executor.submit(() -> {
  System.out.println("Do Something");
  return null;
});
```



**Sample 1**

- `submit( () -> {} )`은 멀티스레드로 처리할 작업을 예약한다. 인자로 람다식을 전달할 수 있다. (Runnable, Callable)

```java
public class ExecutorServiceTest {

  public static void main(String args[]) throws InterruptedException {
    ExecutorService executor = Executors.newFixedThreadPool(4);

    executor.submit(() -> {
      String threadName = Thread.currentThread().getName();
      System.out.println("Job1 " + threadName);
    });
    executor.submit(() -> {
      String threadName = Thread.currentThread().getName();
      System.out.println("Job2 " + threadName);
    });
    executor.submit(() -> {
      String threadName = Thread.currentThread().getName();
      System.out.println("Job3 " + threadName);
    });
    executor.submit(() -> {
      String threadName = Thread.currentThread().getName();
      System.out.println("Job4 " + threadName);
    });

    // 더이상 ExecutorService에 Task를 추가할 수 없습니다.
    // 작업이 모두 완료되면 쓰레드풀을 종료시킵니다.
    executor.shutdown();

    // shutdown() 호출 전에 등록된 Task 중에 아직 완료되지 않은 Task가 있을 수 있습니다.
    // Timeout을 20초 설정하고 완료되기를 기다립니다.
    // 20초 전에 완료되면 true를 리턴하며, 20초가 지나도 완료되지 않으면 false를 리턴합니다.
    if (executor.awaitTermination(20, TimeUnit.SECONDS)) {
      System.out.println(LocalTime.now() + " All jobs are terminated");
    } else {
      System.out.println(LocalTime.now() + " some jobs are not terminated");

      // 모든 Task를 강제 종료합니다.
      executor.shutdownNow();
    }

    System.out.println("end");
  }
}
```

```log
Job1 pool-1-thread-1
Job3 pool-1-thread-1
Job4 pool-1-thread-1
Job2 pool-1-thread-2
end
```



**Sample 2**

- `newSingleThreadExecutor` 를 사용하면 순차적으로 처리된다.

```java
public class ExecutorServiceTest2 {

  public static void main(String args[]) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
      String threadName = Thread.currentThread().getName();
      System.out.println("Job1 " + threadName);
    });
    executor.submit(() -> {
      String threadName = Thread.currentThread().getName();
      System.out.println("Job2 " + threadName);
    });
    executor.submit(() -> {
      String threadName = Thread.currentThread().getName();
      System.out.println("Job3 " + threadName);
    });
    executor.submit(() -> {
      String threadName = Thread.currentThread().getName();
      System.out.println("Job4 " + threadName);
    });

    executor.shutdown();
    executor.awaitTermination(20, TimeUnit.SECONDS);
    System.out.println("end");
  }
}
```

```log
Job1 pool-1-thread-1
Job2 pool-1-thread-1
Job3 pool-1-thread-1
Job4 pool-1-thread-1
end
```
---

###### tags: `과외(하희영)`