아이템 80 스레드보다는 실행자, 태스크, 스트림을 애용하라

Effective Java 3e 아이템 80를 요약한 내용 입니다.

java.util.concurrent 패키지는 실행자 프레임워크라고 하는 인터페이스 기반의 유연한 태스크 실행 기능을 담고 있다.

// 작업 큐를 생성하다. 
ExcutorService exec  = Executors.newSingleThreadExcutor();

// 다음은 이 Excutor에 실행할 태스크(task; 작업)를 넘기는 방법이다.
exec.execute(runnable);

// 다음은 Excutor를 우아하게 종료시키는 방법이다(이 작업이 실패하면 VM 자체가 종료되지 않을 것이다)
exec.shutdown();

실행자 서비스의 기능은 이 외에도 많다.

실행자 서비스의 주요 기능

  • 특정 태스크가 완료되기를 기다린다.

  • 태스크 모음 중 아무것 하나(invokeAny 메서드) 혹은 모든 태스크(invokeALL 메서드)가 완료되기를 기다린다.

  • 실행자 서비스가 종료하기를 기다린다(awaitTermination 메서드)

  • 완료된 태스크들의 결과를 차례로 받는다(ExecutorCompletionService 이용)

  • 태스크를 특정 시간에 혹은 주기적으로 실행하게 한다(ScheculedThread PoolExecutor 이용)

그 외 기능

  • 큐를 둘 이상의 스레드가 처리하게 하고 싶다면 간단히 다른 정적 팩터리를 이용하여 다른 종류의 실행자 서비스(스레드 풀)를 생성하면 된다.

  • 평범하지 않은 실행자를 원한다면 ThreadPoolExecutor 클래스를 직접 사용해도 된다. 이 클래스로는 스레드 풀 동작을 결정하는 거의 모든 속성을 설정할 수 있다.

  • 작은 프로그램이나 가벼운 서버라면 Executors.newCachedThreadPool을 사용하라. 특별히 설정할 게 없고 일반적인 용도에 적합하게 동작한다.

  • 무거운 프로덕션 서버에서는 스레드 개수를 고전한 Executors.newFixedThreadPool을 선택하거나 완전히 통제할 수 있는 ThreadPoolExecutor를 직접 사용하는 편이 훨씬 낫다.

주의해서 사용해야 한다

작업 큐를 손수 만드는 일은 삼가야 하고, 스레드를 직접 다루는 것도 일반적으로 삼가야 한다. 스레드를 직접 다루면 Thread가 작업 단위와 수행 메커니즘 역할을 모두 수행하게 된다. 반면 실행자 프레임워크에서는 작업 단위와 실행 매커니즘이 분리되어 있어서 의미가 명확하다.

  • Runnable

  • Callable

ForkJoinTask

자바 7이 되면서 실행자 프레임워크는 포크-조인 태스크를 지원하도록 확장되었다. 포크-조인 태스크, 즉 ForkJoinTask의 인스턴스는 작은 하위 태스크로 나뉠 수 이고, ForkJoinPool을 구성하는 스레드들이 이 태스크들을 처리하며, 일을 먼저 끝낸 스레드는 다른 스레드의 남은 태스크를 가져와 대신 처리할 수도 있다.

이러한 포크-조인 태스크를 직접 작성하고 튜닝하기란 어려운 일이지만, 포크-조인 풀을 이용해 만든 병렬 스트림을 이용하면 적은 노력으로 그 이점을 얻을 수 있다. 물론 포크-조인에 적합한 형태의 작업이어야 한다.

참조

Last updated