들어가며
java.util.concurrent
패키지에서 간단히 작업 큐를 생성할 수 있다.
1 | ExecutorService exec = Executors.newSingleThreadExecutor(); //생성 |
그 외에도 다양한 기능을 지원한다.
- 특정 태스크가 완료되기를 기다린다.
- 태스크 모음중 아무것 하나(invokeAny) 혹은 모든 태스크(invokeAll)가 완료되기를 기다린다.
- ExecutorService가 종료하기를 기다린다.(awaitTermination)
- 완료된 태스크들의 결과를 차례로 받는다.(ExecutorCompletionService)
- 태스크를 특정 시간에 혹은 주기적으로 실행하게 한다.(ScheduledThreadPoolExecutor)
큐를 둘 이상의 스레드가 처리하게 하고 싶다면 스레드 풀을 이용하면 된다. 더 세세한 설정을 원하면 ThreadPoolExecutor
클래스를 이용할 수 있다. 작은 프로그램이나 서버라면 Executors.newCachedThreadPool
이 좋은 선택이다. 무거운 서버라면 이는 좋지 못하다. 새로운 태스크가 들어온다면 즉시 스레드에 위임되는데 가용할 스레드가 없다면 스레드를 새로 생성한다. 이는 곧 CPU 점유율을 100%로 올리다가 떨어뜨리는 결과를 낳게 되는데 떨어지는 이유는 context switching에 자원을 소모하기 때문이다. 따라서 무거운 서버라면 Executors.newFixedThreadPool
이나 ThreadPoolExecutor
를 직접 사용하는게 좋다.
태스크는 Runnable과 Callable이 있다. Callable은 Runnable과 비슷하지만 값을 반환하고 임의의 예외를 던질 수 있다. 그리고 이 태스크를 수행하는 일반적인 매커니즘이 ExecutorService다. 태스크를 ExecutorService에 맡기면 원하는 태스크 수행 정책을 선택할 수 있고 언제든지 변경이 가능하다.
자바 7부터는 ExecutorService가 fork-join 태스크를 지원한다. fork-join 태스크는 fork-join pool이라는 특별한 ExecutorService가 실행해준다. ForkJoinTask의 인스턴스는 작은 하위 태스크로 나뉠 수 있고 FockJoinPool을 구성하는 스레드들이 이 태스크를 처리하며 일을 먼저 끝낸 스레드는 다른 스레드의 남은 태스크를 가져와 대신 처리할 수도 있다. 이를 통해 CPU 처리량을 높히며 낮은 지연율을 달성할 수 있다.