개발/Java

Java] ExecutorService란?

펭귀니 :) 2021. 4. 12. 19:18

❓ ExecutorService란?

병렬 작업 시 여러 개의 작업을 효율적으로 처리하기 위해 제공되는 JAVA 라이브러리이다.

❔ ExecutorService가 없었다면?

각기 다른 Thread를 생성해서 작업을 처리하고, 처리가 완료되면 해당 Thread를 제거하는 작업을 손수 진행해야하는 것을 ExecutorService 클래스를 이용하면 쉽게 처리가능하다.

🎉 ExecutorService

ExecutorService에 Task만 지정해주면 친절하게 알아서 ThreadPool을 이용해서 Task를 실행하고 관리한다.

🤹 Task는 뭐로 관리가 되나?

Queue로 관리된다.

ThreadPool에 있는 Thread수보다 Task가 많으면, 미실행된 Task는 Queue에 저장되고,

실행을 마친 Thread로 할당되어 순차적으로 수행된다.

⏯️ ExecutorService 사용하기

첫번째 방법. ThreadPoolExecutor로 객체 생성하기

ExecutorService는 인터페이스이기 때문에 구현체인 ThreadPoolExecutor로 초기화할 수 있다.

ExecutorService executorService = new ThreadPoolExecutor(int corePoolSize, 
                                int maximumPoolSize, 
                                long keepAliveTime, 
                                TimeUnit unit, 
                                BlockingQueue<Runnable> workQueue);

ExecutorService executorService = new ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler);

ExecutorService executorService = new ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);

두번째 방법. Executors 클래스에서 제공하는 Static Factory Method(정적 팩토리 메소드) 사용하기

ExecutorService executorService = Executors.newCachedThreadPool();

ExecutorService executorService = Executors.newFixedThreadPool(int nThreads);

ExecutorService executorService = Executors.newSingleThreadExecutor();
  • CachedThreadPool

    • 쓰레드를 캐싱하는 쓰레드풀 (일정시간동안 쓰레드를 검사하여 60초동안 작업이 없으면 Pool에서 제거한다.)

    • CachedThreadPool은 쓰레드수가 폭발적으로 증가할 수 있다는 단점이 있다.

      Thread의 제한 없이 무한정 생성하고, 해당 쓰레드의 작업이 60초간 없을 경우 Pool에서 제거하는 방식이기 때문에 작업이 계속적으로 쌓이는 환경에서는 해당 Thread가 소멸되는 것보다, 생성되는 양이 더 많을 것

  • FixedThreadPool

    • 고정된 개수를 가진 쓰레드풀
    • fixedThreadPool을 생성할때, 해당 머신의 CPU코어수를 기준으로 생성하면 더 좋은 퍼포먼스를 얻을 수 있다.
  • SingleThreadExecutor

    • 한 개의 쓰레드로 작업을 처리하는 쓰레드풀 (TaskPool?)
    • 싱글 쓰레드의 작업을 처리할때 고려해야 할 race-condition이라던지 하는 부분들을 알아서 처리

📝 ExecutorService에 Task(일) 시키기

ExecutorService에 작업을 submit하면, 내부에서 해당 작업을 스케쥴링 하면서 적절하게 일을 처리한다.

ThreadPool에 있는 쓰레드들이 각자 본인의 Task를 가지고 작업을 처리하여, 개발자 입장에서는 쓰레드들의 생명주기를 따로 관리할 필요가 없다.

작업을 할당하기 위해 제공되는 메서드들

  1. execute()
    • 리턴 타입이 void로 Task의 실행 결과나 Task의 상태를 알 수 없다.
  2. submit()
    • Task를 할당하고 Future 타입의 결과값을 받는다. 결과 리턴이 되어야해서 Callable을 구현한 Task를 인자로 준다.
  3. invokeAny()
    • Task를 Collection에 넣어서 인자로 넘겨준다. 실행에 성공한 Task 중 하나의 리턴값을 반환한다.
  4. invokeAll()
    • Task를 Collection에 넣어서 인자로 넘겨줄 수 있다. 모든 Task의 리턴값을 List<Future<>>로 반환한다.

🛑 ExecutorService를 종료

shutdown()이나 shutdownNow()를 사용한다.

  1. shutdown()
    • 실행 중인 모든 Task가 수행되면 종료
  2. shutdownNow()
    • 실행중인 Thread들을 즉시 종료시키려고 하지만 모든 Thread가 동시에 종료되는 것을 보장하지는 않고 실행되지 않은 Task를 반환

두 개의 shutdown 메서드가 결합된 awaitTermination()을 사용하는 것이 추천된다.

이 메서드는 먼저 새로운 Task가 실행되는 것을 막고, 일정 시간동안 실행 중인 Task가 완료되기를 기다린다. 만일 일정 시간동안 처리되지 않은 Task에 대해서는 강제로 종료시킨다.

executorService.shutdown(); 
try { 
    if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) { 
        executorService.shutdownNow(); 
    } 
} catch (InterruptedException e) { 
    executorService.shutdownNow(); 
}

 

참고 링크

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html  

👁‍🗨 추가로 공부할 Keyword

  • BlockingQueue
  • Static Factory Method
  • Factory Method Pattern
  • CPU core별 적절 Thread 수