아이템 80. 스레드보다는 실행자, 태스크, 스트림을 애용하라
java.util.concurrent 패키지가 등장했다. 이 패키지는 **실행자 프레임워크(Executor Framework)**라고 하는 인터페이스 기반의 유연한 태스크 실행 기능을 담고 있다. 뛰어난 작업큐를 다음 단 한 줄로 생성할 수 있다.
ExecutorService exec = Executors.newSingleThreadExecutor();이 실행자에 실행할 태스크(작업)을 넘기는 방법이다.
exec.execute(runnable);그리고 실행자를 종료시키는 방법
exec.shutdown();평범하지 않은 실행자를 원한다면 ThreadPoolExecutor 클래스를 직접 사용해도 된다. 이 클래스로 스레드 풀 동작을 결정하는 거의 모든 속성을 설정할 수 있다. 작은 프로그램이나 가벼운 서버라면 Executors.newCachedThreadPool이 일반적으로 좋은 선택일 것이다. 특별히 설정할 게 없고 일반적인 용도에 적합하게 동작한다. 하지만 CachedThreadPool은 무거운 프로덕션 서버에 좋지 못하다. CachedThreadPool은 요청 받는 태스크들이 큐에 쌓지 않고 즉시 스레드에 위임돼 실행한다. 가용한 스레드가 없다면, 새로 하나를 생성한다. 서버가 아주 무겁다면 CPU 이용률이 100%로 치닫고, 새로운 태스크가 도착하는 족족 또 다른 스레드를 생성하며 상황을 더욱 악화시킨다. 따라서 무거운 프로덕션 서버에서는 스레드 개수를 고정한 Executors.newFixedThreadPool을 선택하거나 완전히 통제할 수 있는 ThreadPoolExecutor를 직접 사용하는 편이 훨씬 낫다.
작업 단위를 나타내는 핵심 추상 개념이 태스크다. 태스크는 두가지가 있다. Runnable과 그 사촌인 Callable(Callable은 Runnable과 비슷하지만 값을 반환하고 임의의 예외를 던질 수 있다.) 그리고 태스크를 수행하는 일반적인 메커니즘이 바로 실행자 서비스다. 핵심은 (컬렉션 프레임워크가 데이터 모음을 담당하듯) 실행자 프레임워크가 작업 수행을 담당해준다는 것이다.
자바7이 되면서 실행자 프레임워크는 포크조인 태스크를 지원하도록 확장되었다. 포크-조인 태스크는 포크-조인 풀이라는 특별한 실행자 서비스가 실행해준다. 포크-조인 태스크의 인스턴스는 작은 하위 태스크로 나뉠 수 있고, ForkJoinPool을 구성하는 스레드들이 이 태스크들을 처리하며, 일을 먼저 끝낸 스레드는 다른 스레드의 남은 태스크를 가져와 대신 처리할 수도 있다. 이렇게 하여 모든 스레드가 바쁘게 움직여 CPU를 최대한 활용하면서 높은 처리량과 낮은 지연시간을 달성한다.
- 자바 병렬 프로그래밍 (에이콘 출판사)