Executor 스레드 풀 관리
ExecutorService의 기본 구현체인 ThreadPoolExecutor의 생성자는 다음 속성을 사용한다.
- corePoolSize : 스레드 풀에서 관리되는 기본 스레드의 수
- maximumPoolSize : 스레드 풀에서 관리되는 최대 스레드 수
- keepAliveTime , TimeUnit unit : 기본 스레드 수를 초과해서 만들어진 초과 스레드가 생존할 수 있는 대기 시간, 이 시간 동안 처리할 작업이 없다면 초과 스레드는 제거된다.
- BlockingQueue workQueue : 작업을 보관할 블로킹 큐


- 작업을 보관할 블로킹 큐의 구현체로 ArrayBlockingQueue(2)를 사용했다. 사이즈를 2로 설정했으므로 최 대 2개까지 작업을 큐에 보관할 수 있다.
- corePoolSize=2 , maximumPoolSize=4를 사용해서 기본 스레드는 2개, 최대 스레드는 4개로 설정했다.
- 스레드 풀에 기본 2개의 스레드를 운영한다. 요청이 너무 많거나 급한 경우 스레드 풀은 최대 4개까지 스레 드를 증가시켜서 사용할 수 있다. 이렇게 기본 스레드 수를 초과해서 만들어진 스레드를 초과 스레드라 하겠다.
- 3000 , TimeUnit.MILLISECONDS
- 초과 스레드가 생존할 수 있는 대기 시간을 뜻한다. 이 시간 동안 초과 스레드가 처리할 작업이 없다면 초과 스레드는 제거된다.
- 여기서는 3000 밀리초(3초)를 설정했으므로, 초과 스레드가 3초간 작업을 하지 않고 대기한다면 초과 스레드는 스레드 풀에서 제거된다.
분석

- task1 작업을 요청한다.
- Executor는 스레드 풀에 스레드가 core 사이즈만큼 있는지 확인한다.
- core 사이즈만큼 없다면 스레드를 하나 생성한다.
- 작업을 처리하기 위해 스레드를 하나 생성했기 때문에 작업을 큐에 넣을 필요 없이, 해당 스레드가 바로 작업을 처리한다.


- 새로 만들어진 스레드1이 task1을 수행한다.


- task2를 요청한다.
- Executor는 스레드 풀에 스레드가 core 사이즈만큼 있는지 확인한다.
- 아직 core 사이즈 만큼 없으므로 스레드를 하나 생성한다.
- 새로 만들어진 스레드2가 task2를 처리한다


- task3 작업을 요청한다.
- Executor는 스레드 풀에 스레드가 core 사이즈만큼 있는지 확인한다.
- core 사이즈만큼 스레드가 이미 만들어져 있고, 스레드 풀에 사용할 수 있는 스레드가 없으므로 이 경우 큐에 작 업을 보관한다.


- task4 작업을 요청한다.
- Executor는 스레드 풀에 스레드가 core 사이즈만큼 있는지 확인한다.
- core 사이즈만큼 스레드가 이미 만들어져 있고, 스레드 풀에 사용할 수 있는 스레드가 없으므로 이 경우 큐에 작업을 보관한다.


- task5 작업을 요청한다.
- Executor는 스레드 풀에 스레드가 core 사이즈만큼 있는지 확인한다. -> core 사이즈만큼 있다.
- Executor는 큐에 보관을 시도한다 -> 큐가 가득 찼다.
- 큐가 가득 차면 긴급 상황이다. 대기하는 작업이 꽉 찰 정도로 요청이 많다는 뜻이다. 이 경우 Executor는 max(maximumPoolSize) 사이즈까지 초과 스레드를 만들어서 작업을 수행한다.
- core=2 : 기본 스레드는 최대 2개
- max=4 :기본스레드 2개에 초과 스레드 2개 합계 총 4개 가능(초과스레드= max - core )
- Executor는 초과 스레드인 스레드 3을 만든다.
- 작업을 처리하기 위해 스레드를 하나 생성했기 때문에 작업을 큐에 넣을 필요 없이, 해당 스레드가 바로 작업을 처리한다.
- 참고로 이 경우 큐가 가득 찼기 때문에 큐에 넣는 것도 불가능하다.
- 스레드 3이 task5를 처리한다.


- task6 작업을 요청한다.
- 큐가 가득 찼다.
- Executor는 초과 스레드인 스레드 4를 만들어서 task6을 처리한다.
- 큐가 가득찼기 때문에 작업을 큐에 넣는 것은 불가능하다.


- task7 작업을 요청한다.
- 큐가 가득 찼다.
- 스레드 풀의 스레드도 max 사이즈만큼 가득 찼다.
- RejectedExecutionException이 발생한다.
이 경우 큐에 넣을 수도 없고, 작업을 수행할 스레드도 만들 수 없다. 따라서 작업을 거절한다.


- 스레드 1이 task1을 스레드 3이 task5의 작업을 완료하고 스레드 풀에 대기 상태로 돌아간다.

- 스레드 풀의 스레드는 큐의 데이터를 획득하기 위해 대기한다.
- 스레드 1: task3을 획득한다.
- 스레드 3: task4를 획득한다.


- 모든 작업이 완료된다.


- 스레드 3, 스레드 4와 같은 초과 스레드들은 지정된 시간까지 작업을 하지 않고 대기하면 제거된다. 긴급한 작업들 이 끝난 것으로 이해하면 된다.
- 여기서는 지정한 3초간 스레드 3, 스레드 4가 작업을 진행하지 않았기 때문에 스레드 풀에서 제거된다.
- 참고로 초과 스레드가 작업을 처리할 때마다 시간은 계속 초기화된다.
- 작업 요청이 계속 들어온다면 긴급한 상황이 끝난 것이 아니다. 따라서 긴급한 상황이 끝날 때까지는 초과 스레드를 살려두는 것이 많은 스레드를 사용해서 작업을 더 빨리 처리할 수 있다.

- 초과 스레드가 제거된 모습이다.=
- 이후에 shutdown() 이 진행되면 풀의 스레드도 모두 제거된다.
스레드 미리 생성하기
응답시간이 아주 중요한 서버라면, 서버가 고객의 처음 요청을 받기 전에 스레드를 스레드 풀에 미리 생성해두고 싶을 수 있다.
스레드를 미리 생성해 두면, 처음 요청에서 사용되는 스레드의 생성 시간을 줄일 수 있다.
ThreadPoolExecutor.prestartAllCoreThreads()를 사용하면 기본 스레드를 미리 생성할 수 있다. 참고로 ExecutorService는 이 메서드를 제공하지 않는다.


'멀티스레드와 동시성' 카테고리의 다른 글
| Ch12. 스레드 풀과 Executor 프레임워크 - Excutor 예외 정책 (0) | 2024.11.02 |
|---|---|
| Ch12. 스레드 풀과 Executor 프레임워크 - Excutor 전략 (0) | 2024.10.29 |
| Ch12. 스레드 풀과 Executor 프레임워크 - ExecutorService 종료 (0) | 2024.10.16 |
| Ch12. 스레드 풀과 Executor 프레임워크 - ExecutorService(작업 컬렉션 처리) (0) | 2024.10.13 |
| Ch12. 스레드 풀과 Executor 프레임워크 - Future (0) | 2024.10.06 |