자바에서 제공하는 util.concurrent 패키지의 인터페이스로 병렬 작업 시 여러개의 작업을 효율적으로 처리하기 위해 제공된다.
다시말해 개발자가 작업을 분리하고, 수행하는 작업을 손수 구현한다면 각기 다른 Thread를 생성해서 작업을 처리하고 처리가 완료되면 Thread를 제거하는 작업을 진행해야 하는데 ExecutorService를 이용하면 쉽게 처리가 가능하다는 말이다. Task(작업)만 지정해주면 알아서 Thread Pool을 생성해서 Task를 관리한다.
쓰레드는 크게 작업의 등록과 실행으로 나눠진다. ExecutorService는 작업 등록을 위한 인터페이스지만 상속받은 Executor 인터페이스가 작업 실행에 대한 책임을 갖고 있기 때문에 해당 책임도 동시에 책임진다. 기본적으로 Thread Pool은 ExecutorService를 구현한다.
Task는 Queue로 관리된다. ExecutorService가 생성한 Thread Pool의 Thread보다 Task가 많을 때 Task는 Queue에서 대기한다.
race condition
동시에 실행되는 두 개 이상의 Thread가 한 메소드에 접근할 때 동기화 메커니즘 없이 접근하려고 하는 상황을 말한다.

ForkJoinPool에서 Thread 내부에 양방향 큐인 데크를 사용하는 이유는 큐는 선입선출이기 때문이다. Thread는 다른 Thread의 데크에서 아직 실행되지 않은 작업을 가져와야 하기 때문에 양방향 큐인 데크를 사용한다.
Executor의 상태 확인과 작업 종료 등 라이프사이클 관리를 위한 메소드를 제공한다.
shoutdown
shoutdownNow
인터럽트(interrup)
- 방해하다는 뜻의 영단어로 컴퓨터 분야에서는 CPU가 어떤 프로그램을 처리하고 있는데 당장 처리해야 하는 프로그램이 생겼을 경우 인터럽트 신호를 보내서 먼저 처리해야 하는 프로그램을 처리한 후 인터럽트 된 프로그램을 이어서 한다.
- CPU를 효율적으로 사용하기 위한 기능이다. 문제 상황을 빠르게 처리할 수 있고 프로그램의 이상 현상을 보다 쉽게 파악할 수 있다.
Thread는 데몬 쓰레드(주 쓰레드를 서포트하기 위해 만들어진 쓰레드로 주 쓰레드가 종료되면 같이 종료된다.)가 아니기 때문에 shoutdown / shoutdownNow을 호출하지 않으면 ExecutorService는 다음 작업까지 계속 대기하게 된다.
또한 해당 작업에 인터럽트 여부에 따른 처리가 정의되어 있지 않으면 작업은 계속 실행된다.
shoutdown / shoutdownNow을 호출 후 ExecutorService는 재사용할 수 없다.
isShoutdown
isTerminated
awaitTermination
비동기란 요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 진행할 수 있는 것이다.
동시에 여러 작업을 실행할 수 있는 메소드다. 비동기 작업의 진행을 추적할 수 있는 Future 인터페이스를 반환한다.
Future 인터페이스
- 작업은 지금 당장 사용할 수 있는 스레드가 없으면 실행이 미뤄질 수 있고, 작업 시간이 오래 걸릴 수도 있다. 그래서 바로 반환값을 받지 못하고 미래의 어느 시점에 받을 수 있는데 미래의 완료된 작업의 반환값을 구하기 위해 사용되는 것이 Future 인터페이스다.
submit
invokeAll
invokeAny
ExecutorService를 사용하면 shutdown/shutdownNow 메소드를 사용해 쉽게 스레드를 종료할 수 있다.
하지만 일반적으로 스레드는 메소드 안에서 시작이 되는데 메소드가 그 스레드를 리턴해버리면 해당 스레드의 위치를 잃어버리게 된다. 그렇게 되면 스레드는 어딘가에서 계속 살아있게 되는데 만약 스레드가 맡은 작업이 무한반복문일 경우 스레드를 종료할 방법이 없어진다.

병렬 작업 처리가 많아지게 되면 Thread가 더 많이 생성되고 이는 메모리 사용량이 계속해서 늘어나게 됨을 의미한다. Thread의 관리를 위해 필요한 것이 바로 Thread Pool이다.
병렬성
병렬성은 크게 데이터 병렬성과 작업 병렬성으로 구분된다.
- 데이터 병렬성
- 전체 데이터를 쪼개 서브 데이터로 만들고, 서브 데이터를 병렬 처리하여 작업을 빠르게 수행하는 것을 말한다. 서브 데이터는 멀티 코어의 수만큼 쪼개져 각각의 데이터들을 분리된 Thread에서 병렬 처리한다.
- 작업 병렬성
- 서로 다른 작업을 병렬 처리하는 것을 말한다. 대표적인 예로는 웹서버로, 각각의 브라우저에서 요청한 내용을 개별 Thread에서 병렬로 처리한다.
Thread Pool은 작업 처리에 필요한 Thread를 제한된 개수만큼 정해 놓고 작업 큐(Queue)에 들어오는 작업에게 Thread를 할당해준다.
서버는 동시에 여러 사람이 접속할 수 있고 접속한 사람의 요청만큼 Thread를 생성해야 한다. 하지만 자바는 Thread를 운영체제의 자원으로 사용하고 있기 때문에 그랬다가는 운영체제의 자원이 빨리 소진된다. Thread Pool은 Thread를 관리하기 위해 필요하다.
Q . Thread Pool의 종류 중 하나인 CachedThreadPool은 60초 마다 Thread를 삭제하잖아. 그럼 아까 말했던 장점이 소용 없는 거 아닌가?
CachedThreadPool은 지속적으로 작업이 반복되는 환경에선 효율적이나 간혈적으로 작업이 일어나는 환격에선 반복적인 Thread의 생성과 종료로 인해 오버헤드가 발생할 수 있기 때문에 오히려 효율성이 떨어진다.
스프링부트엔 내장된 서블릿 컨테이너인 Tomcat이 존재하고 Tomcat 내부에 Thread Pool이 있다. application.yml 또는 application.properties로 Thread Pool을 설정할 수 있다.
server:
tomcat:
threads:
max: 200 # 생성할 수 있는 thread의 총 개수
min-spare: 10 # 항상 활성화 되어있는(idle) thread의 개수
max-connections: 8192 # 수립가능한 connection의 총 개수
accept-count: 100 # 작업큐의 사이즈
connection-timeout: 20000 # timeout 판단 기준 시간, 20초
port: 8080 # 서버를 띄울 포트번호

다른 Thread Pool은 모든 Thread가 작업을 실행 중일 때 요청이 들어오면 작업 큐에 대기시킨다. 하지만 Tomcat의 Thread Pool은 min-spare 이상의 요청이 들어오면 즉시 설정해두었던 max만큼의 Thread를 추가로 생성한다.
사용자 수준 스레드(User Level Threads)
커널 수준 스레드(Kernel Level Threads)
스레드가 실행할 때 커널의 CPU가 CPU에 스레드를 스케줄링 해야 한다. 그런데 CPU는 커널의 일부분이기 때문에 사용자 수준 스레드를 모른다. 이러한 이유로 사용자 수준 스레드는 커널 수준 스레드에 매핑되어 실행된다. 아래는 매핑의 종류를 설명한다.
커널
- 컴퓨터 운영체제의 핵심이 되는 프로그램으로 시스템의 모든 것을 제어한다.
- 보안
- 컴퓨터 하드웨어와 프로세스의 보안을 책임진다.
- 자원 관리
- 자원을 효율적으로 관리하여 프로그램의 실행을 원활하게 진행한다.
- 추상화
- 물리적으로 하나뿐인 하드웨어를 여러 사람이 번갈아 사용할 수 있도록 여러개처럼 보이게 한다.
스케줄링
- 여러 프로세스가 번갈아 사용하는 자원을 어떤 시점에 어떤 프로세스에게 할당할지 결정하는 기법이다.
여러 개의 사용자 수준 스레드를 하나의 커널 수준 스레드에 매핑한다. 사용자 수준 스레드 중 하나라고 블락되면 전체 프로세스가 블락된다.
한 번에 하나의 사용자 수준 스레드만이 커널에 접근할 수 있기 때문에 멀티 코어 시스템에서 병렬 처리가 불가능하다.
하지만 동기화 및 리소스 공유가 쉽기 때문에 시간이 단축된다는 장점이 있다.
java 초기 버전의 스레드 모델인 Green Thread가 다대일 모델이었다. 대부분의 컴퓨터가 멀티 코어인 현재는 사용되지 않는다.
하나의 사용자 수준 스레드를 하나의 커널 수준 스레드에 매핑한다.
하나의 사용자 수준 스레드가 블락되더라도 다른 스레드는 실행이 되기 때문에 병렬 처리가 가능하다.
사용자 수준 스레드를 생성하기 위해 무조건 커널 수준 스레드를 생성해야 한다는 것이 단점이다. 많은 수의 커널 수준 스레드는 시스템이 부담을 줄 수 있다.
여러 개의 사용자 수준 스레드와 같은 수 혹은 그보다 적은 수의 커널 수준 스레드를 연결한다.
커널 수준 스레드의 수는 4코어 시스템보다 7코어 시스템이 더 많다.
가장 높은 정확도의 동시성을 제공하며 앞서 소개한 두 모델의 단점을 절충한다. 하나의 사용자 수준 스레드가 블락되었을 때 커널 수준 스레드는 다른 사용자 수준 스레드의 실행을 스케쥴할 수 있다.
두 수준 모델이라고도 불리는데 일대일 모델로 동작하는 것도 가능하기 때문이다.
하지만 실제로 구현하기 어렵기 때문에 코어의 수가 늘어난 현재로써는 대부분의 운영체제가 일대일 모델로 동작한다.