실행자 프레임워크 (Executor Framework)
java.util.concurrent 패키지는 실행자 프레임워크라고 하는 인터페이스 기반의 유연한 태스크 실행 기능을 담고 있다.
//작업 큐 생성
ExecutorService exec = Executors.newSingleThreadExecutor();
//실행자에 task(작업) 넘기기
exec.execute(runnalbe);
//실행자 종료
exec.shutdown();
단 한 줄로 작업 큐를 생성하고, task를 넘기며, 실행자를 우아하게 종료시킬 수 있다.
실행자 서비스의 주요 기능
- 특정 task가 완료되기를 기다린다.
- task 모음 중 아무것 하나(invokeAny 메서드) 혹은 모든 task(invokeAll 메서드)가 완료되기를 기다린다.
- 실행자 서비스가 종료하기를 기다린다. (awaitTermination 메서드)
- 완료된 task들의 결과를 차례로 받는다. (ExecutorCompletionService 이용)
- task를 특정 시간에 혹은 주기적으로 실행하게 한다. (ScheduledThreadPoolExecutor 이용)
큐를 둘 이상의 스레드가 처리하게 하기
- 정적 팩터리를 이용하여 다른 종류의 실행자 서비스(스레드 풀)를 생성
- 스레드 풀의 스레드 개수는 조정 가능
- ThreadPoolExecutor 클래스는 스레드 풀 동작을 결정하는 거의 모든 속성 설정 가능
execute() vs submit()
여러 가지 클래스
작은 프로그램 또는 가벼운 서버
- Executors.newCachedThreadPool
- 특별히 설정할 게 없고 일반적인 용도에 적합하게 동작
CachedThreadPool은 무거운 프로덕션 서버에는 좋지 못하다.
요청받은 task들이 큐에 쌓이지 않고 즉시 스레드에 위임돼 실행되는 방식.
가용한 스레드가 없다면 새로 하나를 생성하기 때문에 서버가 무거우면 CPU 이용률이 100%로 치닫고 새로운 task가 도착하는 족족 또 다른 스레드를 생성하며 상황이 더욱 악화된다.
무거운 프로덕션 서버
- 스레드 개수를 고정한 newFixedThreadPool
- 완전히 통제할 수 있는 ThreadPoolExecutor를 직접 사용
실행자 프레임워크 특징
작업 큐를 직접 만들거나 스레드를 직접 다루는 것은 일반적으로 삼가야 한다.
스레드를 직접 다루면 Thread가 작업 단위와 수행 메커니즘 역할을 모두 수행하게 된다.
실행자 프레임워크에서는 작업 단위와 실행 메커니즘이 분리된다.
task : 작업 단위를 나타내는 핵심 추상 개념
- Runnable
- Callable (Callable은 Runnable과 비슷하지만 값을 반환하고 임의의 예외를 던질 수 있다.)
실행자 서비스 : task를 수행하는 일반적인 메커니즘
- task 수행을 실행자 서비스에 맡기면 원하는 task 수행 정책을 선택할 수 있고, 언제든지 변경할 수 있다.
포크-조인 (fork-join) 태스크
포크-조인 task는 포크-조인 풀이라는 특별한 실행자 서비스가 실행해 준다.
- ForkJoinTask의 인스턴스는 작은 하위 task로 나뉠 수 있고, ForkJoinPool을 구성하는 스레드들이 이 task들을 처리하며, 일을 먼저 끝낸 스레드는 다른 스레드의 남은 task를 가져와 대신 처리할 수도 있다.
- 모든 스레드가 움직여 CPU를 최대한 활용하면서 높은 처리량과 낮은 지연시간을 달성한다.
- 포크-조인에 적합한 형태의 작업이어야 한다.
포크-조인
포크 (Fork): 큰 작업을 작은 작업들로 분할하는 과정. 이 작은 작업들은 더 작은 단위로 분할될 수도 있다. 이 과정을 재귀적으로 반복하면 작업들이 작은 단위로 나누어진다.
조인 (Join): 작은 작업들을 병렬로 실행한 후에 그 결과를 조합하여 원래 큰 작업을 완성하는 과정을 말한다. 이는 작은 작업들이 모두 실행을 완료하고 결과를 모을 때까지 기다리는 것을 의미한다.
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] Item 82. 스레드 안전성 수준을 문서화하라 (0) | 2023.08.21 |
---|---|
[Effective Java] Item 81. wait와 notify보다는 동시성 유틸리티를 애용하라 (0) | 2023.08.20 |
[Effective Java] Item 79. 과도한 동기화는 피하라 (0) | 2023.08.20 |
[Effective Java] Item 78. 공유 중인 가변 데이터는 동기화해 사용하라 (0) | 2023.08.13 |
[Effective Java] Item 77. 예외를 무시하지 말라 (0) | 2023.08.13 |