Home [Effective Java] Item80. 스레드보다는 실행자, 태스크, 스트림을 애용하라!
Post
Cancel

[Effective Java] Item80. 스레드보다는 실행자, 태스크, 스트림을 애용하라!

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

java.util.concurrent 패키지

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

    • 초판에서 다뤘던 예제인 단순한 작업 큐가 안전 실패나 응답 불가가 될 수 있는 여지를 없애기 위해 무수한 코드를 추가했던 것에 비해 아주 간단하게 이용이 가능해졌다.
  • 초판의 코드보다 모든 면에서 뛰어난 작업 큐를 단 한 줄로 생성할 수 있게 되었다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
      
    public class Main {
        public static void main(String[] args) {
            ExecutorService exec = Executors.newSingleThreadExecutor(); // 뛰어난 작업 큐를 단 한 줄로 생성 가능
            exec.execute(() -> System.out.println("Hello World!")); // 실행할 태스크를 넘기는 방법
          	exec.shutdown(); // 실행자를 우아하게 종료(실패 시 VM 자체가 종료되지 않음)
        }
    }
    

실행자 서비스의 기능

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

스레드 풀

  • 큐를 둘 이상의 스레드가 처리하게 하고 싶다면 간단히 다른 정적 팩터리를 이용하여 다른 종류의 실행자 서비스(스레드 풀)를 생성하면 된다.
  • 스레드 풀의 스레드 개수는 고정할 수도, 필요에 따라 늘어나거나 줄어들게 설정할 수도 있다.
  • 필요한 실행자 대부분은 java.util.concurrent.Executors의 정적 팩터리를 이용해 생성할 수 있을 것이다.
  • 평범하지 않은 실행자를 원한다면 ThreadPoolExecutor 클래스를 직접 사용해도 된다. 이 클래스로는 스레드 풀 동작을 결정하는 거의 모든 속성을 설정할 수 있다.

Executors.newCachedThreadPool

  • 작은 프로그램이나 가벼운 서버라면 Executors.newCachedThreadPool이 일반적으로 좋은 선택일 것이다.
  • 특별한 설정 없이 일반적인 용도에 적합하게 동작한다.
  • 하지만, CachedThreadPool은 요청받은 태스크들이 큐에 쌓이지 않고 즉시 스레드에 위임되어 실행되기 때문에 무거운 프로덕션 서버에는 적합히지 않다. 가용한 스레드가 없다면 새로 하나를 생성하고, 서버가 아주 무겁다면 CPU 이용율이 100%에 치닫고, 새로운 태스크가 도착하는 족족 또 다른 스레드를 생성하며 상황을 더욱 악화시킨다.
  • 따라서, 무거운 프로덕션 서버의 경우, 스레드 개수를 고정한 Executors.newFixedThreadPool을 선택하거나, 완전히 통제할 수 있는 ThreadPoolExecutor를 직접 사용하는 편이 훨씬 낫다.

주의할 점

  • 작업 큐를 손수 만드는 일은 삼가고, 스레드를 직접 다루는 것도 일반적으로 삼가야 한다.

포크 조인(fork-join) 태스크

  • 자바 7부터 실행자 프레임워크는 포크-조인 태스크를 지원하도록 확장되었다.
  • 포크-조인 태스크는 포크-조인 풀이라는 특별한 실행자 서비스가 실행해준다.
  • ForkJoinTask의 인스턴스는 작은 하위 태스크로 나뉠 수 있다. ForkJoinPool을 구성하는 스레드들이 이 태스크를 처리한다. 일을 먼저 끝낸 스레드는 다른 스레드의 남은 태스크를 가져와 대신 처리할 수도 있다. 이렇게 하여 모든 스레드가 바쁘게 움직여 CPU를 최대한 활용하게 하고, 높은 처리량과 낮은 지연시간을 달성한다.
  • (포크-조인에 적합한 형태의 작업이라면) 포크-조인 풀을 이용해 만든 병렬 스트림을 이용하면 적은 노력으로 이러한 이점을 얻을 수 있다.
This post is licensed under younghwani by the author.

[Effective Java] Item79. 과도한 동기화는 피하라!

[Effective Java] Item81. wait와 notify보다는 동시성 유틸리티를 애용하라!

Comments powered by Disqus.