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

신명철·2022년 5월 10일
0

java.util.concurrent 패키지에서 간단히 작업 큐를 생성할 수 있다.

ExecutorService exec = Executors.newSingleThreadExecutor(); //생성

exec.execute(runnable); //작업 할당

exec.shutdown(); //종료

그 외에도 다양한 기능을 제공한다.

  • 특정 태스크가 완료되기를 기다린다.
  • 태스크 모음중 아무것 하나(nvokeAny 메서드) 혹은 모든 태스크(nvokeAll메서드) 가 완료되기를 기다린다.
  • ExecutorService가 종료하기를 기다린다.(awaitTermination 메서드)
  • 완료된 태스크들의 결과를 차례로 받는다.(ExecutorCompletionService 이용)
  • 태스크를 특정 시간에 혹은 주기적으로 실행하게 한다.(ScheduledThreadPoolExecutor 이용)

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

스레드풀의 스레드 개수는 고정할 수도, 필요에 따라 늘어나거나 줄어들게 할 수도 있다. 필요한 실행자 대부분은 java.util.concurrent.Executors의 정적 팩터리들을 이용해서 생성할 수 있다.

더 자세한 설정을 원한다면 ThreadPoolExecutor 클래스를 직접 사용해도 된다. 작은 프로그램이나 서버라면 Executors.newCachedThreadPool이 좋은 선택이다. 무거운 서버라면 이는 좋지 못하다. CachedThreadPool은 요청받은 태스크들이 큐에 쌓이지 않고 즉시 스레드에 위임되는데 가용 스레드가 없다면 스레드를 새로 생성한다.

서버가 아주 무겁다면 CPU 이용률이 100% 에 치닫고, 새로운 태스크가 도착하는 족족 또 다른 스레드를 생성하면서 상황을 더욱 악화시킨다. 따라서 무거운 서버라면 Executors.newFixedThreadPool이나 ThreadPoolExecutor를 직접 사용하는게 좋다.

태스크는 RunnableCallable이 있다. CallableRunnable과 비슷하지만 값을 반환하고 임의의 예외를 던질 수 있다. 그리고 이 태스크를 수행하는 일반적인 매커니즘이 실행자 서비스, (ExecutorService)다. 태스크를 ExecutorService에 맡기면 원하는 태스크 수행 정책을 선택할 수 있고 언제든지 변경할 수 있다.

자바7이 되면서 실행자 프레임워크(ExecutorService)는 fork-join 태스크를 지원한다. fork-join 태스크fork-join pool 이라는 특별한 ExecutorService가 실행해준다. ForkJoinTask의 인스턴스는 작은 하위 태스크로 나뉠 수 있고 ForkJoinPool을 구성하는 스레드들이 이 태스크를 처리하며 일을 먼저 끝낸 스레드는 다른 스레드의 남은 태스크를 가져와 대신 처리할 수도 있다. 이를 통해 CPU 처리량을 높히며 낮은 지연시간을 달성할 수 잇따.

profile
내 머릿속 지우개

0개의 댓글