Effective Java : 아이템 80. 스레드보다는 실행자, Task, Stream을 애용하라.

    아이템 80. 스레드보다는 실행자, Task, Stream을 애용하라.

    • 쓰레드는 작업 단위 / 실행 메커니즘을 모두 개발자가 작성해야 함. 
    • 자바는 각종 실행 메커니즘을 추상화한 ExeuctorService를 지원함. 개발자는 이것을 사용하는게 좋음.
    • ExecutorService에는 작업 단위(Runnable, Callable)을 제출해서 사용하도록 하자. 개발자는 작업 단위(Runnable, Callable)만 선언하면 됨. 
    • 서버의 사용 환경에 맞게 적절한 ExecutorService를 사용하는 것이 중요
      • 소규모 서버 → CachedThreadPool
      • 대규모 서버 → FixedThreadPool

     


    자바의 Thread는 작업 단위 + 수행 메커니즘을 모두 표현함. 

    public static void main(String[] args) {
        // Task 정의
        Thread thread = new Thread(() -> System.out.println("hello"));
    
        // 실행 메커니즘 정의.
        thread.start();
    }

    자바의 Thread는 작업 단위와 실행 메커니즘을 모두 표현한다. 위의 코드에서 살펴보면 다음과 같이 분리되어서 각각 실행되는 것을 알 수 있다. 

    • new Thread() : 작업해야 할 Task를 정의함.
    • thread.start() : 쓰레드의 실행 메커니즘을 정의함. 

    예전에는 쓰레드를 직접 사용하면서 작업 단위와 수행 메커니즘을 직접 관리했어야 했다. 이런 부분은 굉장히 곤혹스러울 수 있다. 그러나 자바에서 ExecutorService가 지원되기 시작했는데, ExecutorService는 실행 메커니즘을 담당한다. 이제 개발자는 Task만 선언하고, 실행 메커니즘은 ExecutorService에 맡기면 된다.

     


    자바의 ExeuctorService

    ExecutorService는 쓰레드가 수행 해야 할 작업의 실행 메커니즘을 담당하는 녀석이다. 이미 여러 실행 메커니즘을 지원하는 ExecutorService들이 선언되어 있으며, 개발자는 각 ExeuctorService의 특성을 파악하고 어플리케이션에 맞게 가져다 사용하면 된다. 

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    ExecutorService executorService1 = Executors.newCachedThreadPool();
    ExecutorService executorService2 = Executors.newFixedThreadPool(10);

    예를 들어 cachedThreadPool은 소규모 어플리케이션에 적합하다. CachedThreadPool은 새로운 작업 요청이 들어왔을 때, 처리할 쓰레드가 없으면 새로운 쓰레드를 생성한다. 작업이 밀려들어오고, 처리되지 못한 작업들이 많아서 CPU가 100%를 사용하기 시작하면 작업 요청이 올 때 마다 쓰레드를 생성하면서 점점 처리할 수 없게 된다. 만약에 많은 요청이 오는 서버에서 사용한다면 FixedThreadPool을 사용하는 것이 좋다. 

    이처럼 이미 ExeuctorService는 쓰레드가 수행해야 할 실행 메커니즘을 이미 잘 구현해두었다. 개발자는 이것을 가져다 쓰기만 하면 된다. 


    ExeuctorService를 사용하면 Task를 사용하자

    쓰레드는 작업 / 실행 메커니즘으로 나누어져 있다. ExecutorService는 실행 메커니즘을 담당하고, 개발자는 작업(Task)만 정의해서 ExecutorService에 제출하면 된다. 여기서 Task는 Runnable / Callable로 나누어진다. 각각은 다음 차이가 있다.

    • Runnable : 리턴 타입이 없고, 예외도 없음.
    • Callable : 리턴 타입이 있고, 예외도 던질 수 있음. 

    Callable이라는 작업 단위를 하나 만들어서 ExeuctorService에 제출해서 결과를 받아보는 코드는 아래에서 확인할 수 잇다.

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    
    // 작업 단위 정의
    Callable<String> stringCallable = () -> {
        System.out.println("hello");
        return "hello";
    };
    
    Future<String> submit = executorService.submit(stringCallable);
    try {
        String result = submit.get();
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }

    댓글

    Designed by JB FACTORY