[Java8] Chapter 6-2. Executors

 

✍️ Executors

Thread나 Runnable처럼 Low-Level API를 직접 다루는 것이 아닌, 쓰레드를 만들고 관리하는 작업을 고수준 API, Executors에 위임하는 것

 

  • Executors, 고수준(High-Level) Concurrency 프로그래밍
    • 쓰레드를 만들고 관리하는 작업을 어플리케이션에서 분리한다.
    • Executors가 쓰레드를 만들고 개발자는 Runnable에 해야 할 일을 정의해서 넘겨준다.
  • Executors가 하는 일
    • 쓰레드 만들기 : 어플리케이션이 사용할 쓰레드 풀을 만들어 관리한다.
    • 쓰레드 관리 : 쓰레드 생명 주기를 관리한다.
    • 작업 처리 및 실행 : 쓰레드로 실행할 작업을 제공할 수 있는 API를 제공한다.
  • 주요 인터페이스
    • Executor : execute(Runnable)
    • ExecutorService : Executor를 상속받은 인터페이스로, Callable도 실행할 수 있으며, Executor를 종료시키거나, 여러 Callable을 동시에 실행하는 등의 기능을 제공한다.
    • ScheduledExecutorService : ExecutorService를 상속받은 인터페이스로 특정 시간 이후에 또는 주기적으로 작업을 실행할 수 있다.

 

🍊 Executors 예제 코드

Executors.newSingleThreadExecutor(), 쓰레드를 하나만 사용하는 Executors

execute 혹은 submit 메서드에 Runnable을 던져주면 해당 작업을 실행한다.

ExecutorService executorService = Executors.newSingleThreadExecutor();

executorService.execute(() -> {
	System.out.println("new Thread : " + Thread.currentThread().getName());
});

// graceful shutdown
executorService.shutdown();

// hard shutdown
executorService.shutdownNow();

 

ExecutorService는 어떤 작업을 실행하고 나면 그다음 작업이 들어올 때까지 대기하기 때문에 프로세스가 죽지 않는다. 명시적으로 ExecutorService를 shutdown 해야 하는데, 기본적으로 shutdown은 graceful shutdown으로 현재 진행 중인 작업을 마무리하고 ExecutorService를 종료하는 반면 shutdownNow는 현재 진행 중인 작업을 무시하고 강제 종료하는 hard shutdown이다. 

 

Executors.newFixedThreadPool(), 쓰레드를 n개 사용하는 Executors

두 개의 쓰레드를 가지고 있는 ExecutorService를 만들어 다섯 개의 작업을 보내면 두 개의 쓰레드가 번갈아가며 작업을 수행한다. 그렇다면, 쓰레드의 수보다 많은 작업이 들어올 때 ExecutorService는 어떻게 처리하는 걸까?

ExecutorService에 작업이 들어오면 Blocking Queue에 작업들을 차곡차곡 쌓아놓고 잉여 쓰레드에게 작업을 할당하며  하나씩 작업을 처리해간다.

// 쓰레드를 두 개 가지고 있는 ExecutorService
ExecutorService executorService = Executors.newFixedThreadPool(2);

executorService.execute(getRunnable("hello"));
executorService.execute(getRunnable("world!"));
executorService.execute(getRunnable("I'm"));
executorService.execute(getRunnable("Kang"));
executorService.execute(getRunnable("from Korea!"));

executorService.shutdown();

// hello : pool-2-thread-1
// I'm : pool-2-thread-1
// world! : pool-2-thread-2
// from Korea! : pool-2-thread-2
// Kang : pool-2-thread-1
private static Runnable getRunnable(String message) {
	return () -> System.out.println(message + " : " + Thread.currentThread().getName());
}

 

Executors.newSingleThreadScheduledExecutor(), 특정 시간 이후 혹은 주기적으로 작업을 처리하는 Executors

3초 뒤에 작업을 처리

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

// 3초 뒤에 작업을 실행
scheduledExecutorService.schedule(getRunnable("hello"), 3, TimeUnit.SECONDS);

 

혹은 초기 딜레이 1초, 3초 주기로 작업을 처리할 수도 있다.

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

// 초기 딜레이는 1초, 주기는 3초마다 작업을 실행
scheduledExecutorService.scheduleAtFixedRate(getRunnable("hello"), 1, 3, TimeUnit.SECONDS);

 

👋 정리

CompletedFuture를 학습하기 위해 Callable이 등장해야 한다.

지금까진 우린 Runnable만을 사용해왔다. Runnable의 추상 메서드는 반환형이 void로 반환 값이 없다. 만약 별도의 쓰레드에서 어떤 작업을 수행하고 값을 반환해야 한다면 Runnable로 문제를 해결할 수 있을까?

 

여기에 대한 해답이 Callable이다. Callable은 Runnable과 매우 유사하나 차이점은 Callable의 추상메서드는 반환 타입이 존재한다는 것이다. 

 

 

 

본 내용은 백기선님의 자바8 강의 내용입니다.

 

더 자바, Java 8 - 인프런 | 강의

자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이

www.inflearn.com