JAVA/비동기 프로그래밍

[Java] 자바 동시성 프레임워크

꾸준함. 2024. 4. 20. 21:46

ThreadPool

  • 다수의 쓰레드를 미리 생성하고 관리하여 작업을 효율적으로 처리하는 디자인 패턴
  • 자바에서는 쓰레드 풀을 사용할 수 있도록 Executor 프레임워크 제공

 

1. ThreadPool이 필요한 이유

 

  • 쓰레드 생성 비용 절감
  • 쓰레드 재사용
  • 동시성 제어
  • 대량 요청으로부터 시스템 보호

 

1.1 쓰레드 생성 비용 절감

  • 쓰레드 생성은 비싼 작업
  • 따라서 쓰레드 풀은 쓰레드를 미리 생성하고 초기화하여 대기 상태로 유지함으로써 쓰레드 생성 비용을 절감시킴

 

1.2 쓰레드 재사용

  • 쓰레드 풀은 작업이 종료된 쓰레드를 대기 상태로 전환시킨 뒤 재사용하며 반복적인 쓰레드 생성 및 삭제에 따른 오버헤드를 피하고 성능 향상 도모

 

1.3 동시성 제어

  • 쓰레드 풀은 동시에 실행되는 쓰레드의 개수를 제어함에 따라 시스템 리소스의 과도한 사용을 방지
  • 쓰레드 간 경합으로 인한 성능 저하 또한 예방

 

1.4 대량 요청으로부터 시스템 보호

  • 클라이언트의 동시 접속이 증가하더라도 리소스가 허용하는 한도 내에서 요청을 대기시켜 시스템 오류 방지

 

2. ThreadPool 구현 시 고려해야 하는 요소

 

  • 쓰레드 생성 및 관리 메커니즘
  • 작업 큐
  • 동기화 메커니즘
  • 쓰레드 풀 설정
  • 쓰레드 종료 및 리소스 관리 메커니즘
  • 오류 처리 메커니즘

 

2.1 쓰레드 생성 및 관리 메커니즘

  • 쓰레드 풀을 구현하기 위해 쓰레드 생성, 초기화, 재사용 및 제거 등을 효율적으로 처리할 수 있어야 함

 

2.2 작업 큐

  • 작업들을 관리하는 대기열인 작업 큐 필요
  • 작업 큐는 작업들을 저장하고 쓰레드가 작업을 가져와 실행할 수 있는 구조 제공

 

2.3 동기화 메커니즘

  • 쓰레드 간의 경합 상황을 관리하고 동기화를 처리하여 데이터의 일관성 및 정확성 보장할 수 있어야 함

 

2.4 쓰레드 풀 설정

  • 동시에 실행되는 쓰레드의 최대 갯수, 작업 큐의 크기, 그리고 작업 우선순위 등을 조절하는 매개변수

 

2.5 쓰레드 종료 및 리소스 관리 메커니즘

  • 쓰레드 풀에 속한 쓰레드가 종료되거나 더 이상 필요하지 않을 경우 처리하고 관리할 수 있어야 함

 

2.6 오류 처리 메커니즘

  • 작업 실행 중에 오류가 발생했을 때 적절한 조치를 취할 수 있어야 함

 

https://jenkov.com/tutorials/java-concurrency/thread-pools.html

 

* 주의: 가용 쓰레드가 전부 작업 중인 상태에서 작업 큐도 꽉 찰 경우 오류가 발생해 비정상적 종료가 발생할 수 있음

 

Executor

  • Exeuctor 프레임워크는 java.util.concurrent 패키지에 포함된 쓰레드 관리와 병렬 처리를 위한 고급 기능들을 제공하는 포괄적인 라이브러리
    • 복잡한 쓰레드 생성, 관리, 그리고 동기화 등의 작업을 단순화
    • 성능 향상하기 위한 다양한 클래스 및 인터페이스 제공

 

1. Executor 프레임워크 구조

 

1.1 Executor

  • Executor 프레임워크의 핵심 인터페이스
  • 단일 메서드 execute(Runnable commanad)를 정의
  • 작업 제출 시 Executor 구현체가 적절한 쓰레드를 생성하고 작업을 실행
  • 작업의 제출작업의 실행(실행 방법, 쓰레드 사용, 스케줄링 등의 세부사항)을 분리하는 방법 제공
    • execute 메서드에 Runnable 객체를 넘기는 것을 작업 제출
    • Runnable 명령은 Executor 구현 방식에 따라 새 쓰레드, 풀 쓰레드 또는 호출 쓰레드에서 실행 가능하며 이를 작업 실행으로 구분
    • ex) Runnable의 run() 메서드를 통해 실행하면 main 쓰레드에서 직접 실행
    • ex) thread.start() 메서드를 통해 실행하면 쓰레드를 생성해서 비동기 실행
    • ex) threadPool.submit(runnable) 메서드를 통해 실행하면 쓰레드 풀에 등록해서 비동기 실행
    • 정리하자면 직접 쓰레드를 실행하고 작업을 실행하는 것이 아니라 작업 제출 시 쓰레드 생성과 작업 실행을 Executor에서 처리하도록 위임하는 것이 더 유연하고 좋은 설계이기 때문에 Executor는 내부적으로 작업의 제출과 실행을 분리

 

1.2 Executor 구현체

  • 쓰레드 풀의 생성, 관리, 작업 큐, 그리고 쓰레드 생성 및 삭제 정책 등을 다양한 설정으로 제어 가능
  • ex) ThreadPoolExecutor, ScheduledThreadPoolExecutor

 

1.3 ExecutorService

  • Executor의 한계를 보완해주는 확장 버전
  • 종료를 관리하는 메서드와 하나 이상의 비동기 작업 진행 상황을 추적하는 데 사용할 수 있는 Future를 생성할 수 있는 메서드를 제공하는 Executor
  • Executor에서 제공하지 못하는 작업의 제출(submit)과 쓰레드 풀의 종료를 관리하기 위한 메서드들을 추가 제공
    • 작업 제출과 실행에 더해 작업의 상태 추적 작업 결과 반환, 작업 취소 등 다양한 작업 관리 기능을 제공
    • ExecutorService는 작업 제출부터 작업 실행, 작업 완료, 쓰레드 풀 종료, 자원 회수까지의 과정을 포함하는 라이프 사이클을 가지고 있음

 

1.4 ScheduledExecutorService

  • ExecutorService의 확장 버전
  • 특정 시간 또는 주기적으로 작업을 실행할 수 있도록 스케줄링 메서드 제공

 

Executor 프레임워크

 

Runnable & Callable

  • Runnable과 Callable은 모두 별도의 쓰레드에서 실행할 수 있는 작업을 나타내는 데 사용되는 인터페이스

 

1. Runnable vs Callable

 

기능 Runnable Callable
메서드 시그니처 run() 메서드를 정의하며 인수 없음 call() 메서드를 정의하며 인수가 없고 결과와 예외 구문 있음
예외 처리 Checked Exception 예외를 던질 수 없음 Chekced Exception 예외를 던질 수 있음
용도 쓰레드에서 실행할 작업 정의 결과를 반환하며 예외를 처리해야 하는 작업 정의
결과 반환 작업이 완료되더라도 결과 반환 X 작업 완료 시 결과를 반환하며  Future로 결과 추적 가능

 

 

 

2. Callable & Future

 

2.1 전체적인 flow

  • 메인 쓰레드가 ExecutorService를 통해 Callable을 제출
  • ExecutorService 내부적으로 Callable을 실행하여 결과를 FutureTask에 저장
  • 해당 결과는 Future를 통해 메인 쓰레드로 반환

 

전체적인 Flow

 

2.2 FutureTask의 run() 메서드

 

 

Future & Callback

  • Future와 Callback은 비동기 프로그래밍에서 사용되는 패턴
  • 비동기 작업의 결과를 처리하거나 작업이 완료되었을 때 수행할 동작을 정의하며 사용
  • 자바에서는 Future 인터페이스와 구현체들을 제공하고 있으며 다양한 Callback 패턴 활용
  • Future는 비동기 작업에서 쓰레드 간 결과를 받을 때 유용
  • 비동기 작업 쓰레드 간 실행의 흐름이 독립적이기 때문에 비동기 작업의 완료 시점에 결과를 얻을 수 있어야 하는데 이때 Callback이 유용

 

1. Future vs Callback

 

구분 Future Callback
정의 비동기 작업의 결과를 나타내는 객체 비동기 작업이 완료되었을 때 수행할 동작을 정의한 인터페이스 혹은 클래스
블로킹 여부 비동기 작업이 완료될 때까지 블로킹
(아직 결과값이 구해지지 않은 상태에서 get() 호출 시 blocking)
블로킹되지 않고 비동기 작업이 완료되면 콜백 호출
작업 결과 비동기 작업이 완료되면 결과를 얻을 수 있음 콜백 메서드를 통해 작업 결과를 처리
활용 용도 결과를 받아오는 작업에서 활용 비동기 작업의 완료 후 동작을 정의할 때 주로 활용

 

2. Future를 활용한 비동기 작업

 

 

특징

  • Future 객체는 바로 생성되는 반면 결괏값은 작업이 완료되어야 세팅됨
  • 따라서 future.get() 메서드를 통해 결과를 반환받을 때 blocking 구간이 존재

 

3. Callback을 활용한 비동기 작업

 

 

특징

  • Future와 달리 blocking 구간 존재 X
  • Callback의 경우 결괏값이 다 반환되어야 Callback 인스턴스 생성

 

4. Future와 Callback을 혼합한 비동기 작업

 

 

특징

  • Main Thread -> Callback -> Future 순으로 호출하는 작업
  • Callback에서 Future를 호출하므로 Callback에서 결괏값을 반환받는 과정에서 blocking 구간이 존재하지만 Main Thread 입장에서는 blocking 구간 존재 X

 

Future 구조와 API

  • Future는 비동기 작업의 결과를 나중에 가져올 수 있도록 도와주는 인터페이스
  • Future는 비동기 작업이 완료되었는지 여부 확인 가능하며 조건에 따라 작업을 취소할 수도 있고 작업의 결과를 얻는 방법을 제공
  • 기존의 Future는 작업의 결과를 가져올 때까지 blocking 되며 여러 작업을 조합하는 문제, 예외 처리의 어려움 등이 존재
    • 이러한 단점을 보완하기 위해 자바 8+ 버전부터는 CompletableFuture와 같은 개선된 비동기 도구들이 제공

 

1. Future API

 

1.1 boolean cancel(boolean mayInterruptIfRunning)

  • 작업이 시작되지 않은 경우 해당 작업은 실행되지 않으며 true를 반환
  • 작업이 이미 완료되었거나 취소되었거나 다른 이유로 취소할 수 없는 경우 아무런 일도 발생하지 않으며 false 반환
  • 작업이 이미 시작된 경우 mayInterruptIfRunning 매개변수에 따라 결정
    • true일 경우 해당 작업을 중지시키기 위해 현재 작업을 실행 중인 쓰레드 interrupt 하며 작업 결과를 가져올 때 취소 예외 발생
    • false일 경우 진행 중인 작업을 완료할 수도 있지만 작업 결과를 가져올 때 취소 예외 발생
    • 정리하자면 결괏값을 가져오지 못할 때 취소 성공이라고 간주해도 됨

 

  • 메서드의 반환 값은 작업이 현재 취소되었는지 여부를 반드시 나타내지 않기 때문에 정확한 결과는 isCancelled 메서드를 사용하는 것을 권장
  • 작업과 연관된 모든 대기 중인 쓰레드를 깨우고 시그널을 보내며 done()을 호출하고 callable을 null로 설정

 

1.2 boolean isCancelled()

  • 해당 작업이 정상적으로 완료되기 전에 취소되었으면 true 반환
  • 취소 여부를 명확하게 확인할 때 사용

 

1.3 boolean isDone()

  • 작업이 완료될 경우 true를 반환
  • 정상 종료, 예외 또는 취소를 포함한 모든 경우에 true를 반환
  • 완료라는 개념 내 정상 종료, 예외, 취소가 들어감
    • 정상종료일 경우 FutureTask의 set 메서드에서 이루어지고
    • 예외 발생 시 FutureTask의 setException 메서드에서 이루어지며
    • 취소 시 FutureTask의 cancel() 메서드에서 이루어짐

 

1.4 V get() throws InterruptedException, ExecutionException

  • 작업 결과를 반환하며 작업이 완료될 때까지 쓰레드 대기 (blocking 구간 존재)
  • 작업이 예외를 던져 실패하면 해당 예외가 Future.get()을 호출할 때 발생
  • 실패했을 경우 실패한 정보나 결괏값을 Future에서 얻어올 수 없고 예외 구문에서 확인해야 함
    • CancellationException: 작업이 취소된 경우
    • ExecutionException: 작업이 예외를 발생시킨 경우
    • InterruptedException: 현재 쓰레드가 대기 중에 interrupt 된 경우

 

1.5 V get(long timeout, TimeUnit unit)

  • 지정한 시간 동안 대기하고 해당 시간 내 완료되면 작업 결과를 반환
  • 시간 내 완료되지 않으면 TimeoutException 예외 발생

 

2. FutureTask의 상태 변수

 

FutureTask

 

2.1 작업에 따른 상태 변화

  • 정상 완료: NEW -> COMPLETING -> NORMAL
  • 예외 발생: NEW -> COMPLETING -> EXCEPTIONAL
  • 작업 취소 인자가 true인 경우: NEW -> INTERRUPTING -> INTERRUPTED
  • 작업 취소 인자가 false인 경우: NEW -> CANCELLED

 

2.2 정상 완료 (set)

 

 

2.3 예외 발생 (setException)

 

 

2.4 작업 취소 (cancel)

 

 

3. Future 예제

 

 

ExecutorService

  • 비동기 작업을 실행하고 관리하기 위한 두 가지 메서드 제공
    • void execute(Runnable runnable): 작업을 제출하면 작업을 실행하고 종료
    • Future submit(Callable callable): 작업을 제출하면 작업을 실행과 동시에 결괏값을 포함한 Future를 반환
      • 앞서 Future와 Callback을 혼합한 비동기 작업 참고

 

1. execute() vs submit()

 

기능 execute() submit()
작업 유형 Runnable 작업을 ThreadPool에서 실행 Runnable 또는 Callable 작업을 ThreadPool에서 실행
작업 완료 대기 작업을 수행하고 완료되기를 기다리지 않음 작업이 완료될 때까지 결과를 기다릴 수 있음
결과 반환 결과를 반환하지 않음 작업의 결과를 Future로 반환하여 추후에 결과를 가져올 수 있음
반환 결과 void Future 객체

 

 

1.1 Future<T> submit(Callable<T> task)

  • Callable 작업을 실행하는 메서드
  • 작업의 결과를 나타내는 Future를 반환하며 Future에는 작업의 결과가 저장되어 있음

 

1.2 Future<T> submit(Runnable task, T result)

  • Runnable 작업을 실행하는 메서드
  • 작업의 결과를 나타내는 Future를 반환하며 Future에는 작업의 결과가 저장되어 있음

 

1.3 Future<?> submit(Runnable task)

  • Runnable 작업을 실행하는 메서드
  • 작업의 결과를 나타내는 Future를 반환하며 Future에는 아무런 결과가 존재하지 않음
  • execute()와 차이가 없는 메서드

 

 

2. ThreadPool 중단 및 종료

 

  • ExecutorService는 쓰레드 풀 종료를 위해 두 가지 메서드 제공
    • void shutdown()
    • List<Runnable> shutdownNow()

 

2.1 void shutdown()

  • 정상적인 쓰레드 풀 종료를 위한 메서드
  • 실행 중인 쓰레드를 강제로 interrupt 하지 않기 때문에 인터럽트에 응답하는 작업이나 InterruptedException 예외 구문을 작성할 필요 없음
  • 이전에 제출된 작업은 실행하고 더 이상 새로운 작업은 수락하지 않음
    • 작업이 모두 완료될 경우 ExecutorService 종료
    • graceful shutdown

 

2.2 List<Runnable> shutdownNow()

  • 이전에 제출된 작업을 취소하고 현재 실행 중인 작업도 중단시키려고 시도한 뒤 작업 대기 중이었던 작업 목록을 반환
  • 실행 중인 쓰레드를 강제로 인터럽트 하지만 해당 작업이 인터럽트에 응답하는 작업이 아닌 경우 작업 종료를 보장하지 않음
    • 작업 종료를 위해 Thread.isInterrupted() 혹은 sleep()과 같은 인터럽트 API 사용해야 함
    • interrupt에 InterruptedException이 발생하는 코드 구문이 있어야지만 즉시 종료

 

2.3 shutdown 이후

  • shutdown 후 작업 제출 시도 시 RejectedExecutionException 예외 발생
  • shutdown 호출한 쓰레드는 실행 중인 작업이 종료될 때까지 기다리지 않고 바로 다음 라인 실행
    • 만약 쓰레드가 메서드 호출 후 blocking 되길 원한다면 awaitTermination()를 사용해야 함

 

3. ExecutorService 작업 종료 대기 및 확인

 

  • ExecutorService는 작업 종료 대기 및 확인을 위한 메서드 제공

 

3.1 boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException

  • 종료 요청 후 모든 작업이 실행 완료될 때까지 또는 타임아웃이 발생하거나 현재 쓰레드가 interrupt될 때까지 블로킹
  • 종료가 완료되면 true를 반환
  • 종료가 타임아웃 발생 전에 완료되지 않으면 false를 반환
  • shutdown 호출한 시점 이후 대기하고 싶을 경우 awaitTermination 호출
    • timeout 이후에도 살아 있으면 shutdownNow() 호출

 

3.2 boolean isShutdown()

  • ExecutorService의 shutdown() 혹은 shutdownNow() 메서드가 호출 후 종료 절차가 시작되었는지를 나타냄
  • 종료 절차 중이거나 완전히 종료된 상태일 때 true 반환

 

3.3 boolean isTerminated()

  • ExecutorService가 완전히 종료되어 더 이상 어떠한 작업도 수행하지 않는 상태인지를 나타냄
  • 모든 작업과 쓰레드가 완전히 종료된 후에 true를 반환
  • shutdown 또는 shutdownNow가 먼저 호출되지 않은 경우에는 isTerminated가 절대 true가 되지 않음

 

 

4. ExecutorService 다중 작업 처리

  • ExecutorService는 여러 작업을 실행하고 결과를 수집하는 데 사용되는 메서드를 제공
    • List<Future> invokeAll(Collection tasks) throws InterruptedException
    • List<Future> invokeAll(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException
    • T invokeAny(Collection tasks) throws InterruptedException, ExecutionException
    • T invokeAny(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException

 

4.1 List<Future> invokeAll(Collection tasks) throws InterruptedException

  • 여러 개의 Callable 작업을 동시에 실행
  • 모든 작업이 완료될 때까지 블록 되며 각 작업의 결과를 나타내는 Future 객체의 리스트 반환
  • 작업 완료 시 Future.isDone()이 true가 되며 작업은 정상적으로 종료되거나 예외를 던져 종료될 수 있음
  • 대기 중에 interrupt가 발생한 경우 아직 완료되지 않은 작업들은 취소됨

 

4.2 List<Future> invokeAll(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException

  • 4.1과 기능은 동일하고 시간 경과와 관련된 부분문 차이 남
  • 타임아웃이 발생하거나 모든 작업이 완료될 때까지 블록 되며 각 작업의 결과를 나타내는 Future 객체의 리스트를 반환
  • 타임아웃이 발생한 경우 작업 중 일부는 완료되지 않을 수 있으며 완료되지 않은 작업은 취소됨

 

* 4.1과 4.2의 invokeAll() 메서드 실행 시간은 작업들 중 가장 오래 걸리는 작업만큼 시간이 소요됨

 

 

4.3 T invokeAny(Collection tasks) throws InterruptedException, ExecutionException

  • 여러 개의 Callable 작업을 동시에 실행하고 그중 가장 빨리 성공적으로 완료된 작업의 결과를 반환
  • 어떤 작업이라도 성공적으로 완료하면 block을 해제하고 해당 작업의 결과 반환
  • 정상적인 반환 또는 예외 발생 시 완료되지 않은 작업들은 모두 취소

 

4.4 T invokeAny(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException

  • 4.3 메서드와 기능은 동일하고 시간 경과와 관련된 부분만 차이 남
  • 주어진 시간이 경과하기 전에 예외 없이 성공적으로 완료된 작업의 결과를 반환

 

* 4.3과 4.4의 invokeAny()는 작업들 중 가장 짧게 걸리는 작업만큼 시간이 소요됨

 

 

코드 부연 설명

  • task들을 다 실행은 했기 때문에 interruptThread에 의해 Task 1과 Task 3가 interrupt 신호를 받고 InterruptedException 발생
  • Task 2가 가장 빨리 작업을 완료하고 결과를 반환했으므로 결괏값은 Task 2

 

ScheduledExecutorService

  • 주어진 지연 시간 후에 명령을 실행하거나 주기적으로 실행할 수 있는 ExecutorService를 상속한 인터페이스
  • 작업의 예약 및 실행을 위한 강력한 기능 제공
  • 시간 기반 작업을 조절하거나 주기적인 작업을 수행하는데 유용

 

1. ScheduledExecutorService API

 

  • ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
  • ScheduledFuture<?> schedule(Callable callable, long delay, TimeUnit unit)
  • ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
  • ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

 

1.1 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

  • 주어진 지연 시간 이후 Runnable 작업을 예약하고 ScheduledFuture 반환
  • 예약된 작업은 한 번만 실행
  • command: 실행할 작업
  • delay: 실행을 지연할 시간
  • unit: 지연 매개변수의 시간 단위
  • ex) ScheduledFuture<?> future = ScheduledExecutorService.schedule(task, 5, TimeUnit.SECONDS);
    • 5초 후에 태스크 실행하고 Future 반환

 

1.2 ScheduledFuture<?> schedule(Callable callable, long delay, TimeUnit unit)

  • 주어진 지연 시간 이후 Callable 작업을 예약하고 ScheduledFuture 반환
  • 예약된 작업은 한 번만 실행
  • callable: 실행할 작업
  • delay: 실행을 지연할 시간
  • unit: 지연 배매개변수의 시간 단위
  • ex) ScheduledFuture<?> future = ScheduledExecutorService.schedule(callable, 5, TimeUnit.SECONDS);
    • 5초 후에 함수 실행하고 Future 반환

 

1.3 ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

  • 초기 지연 시간 이후 Runnable 작업을 주기적으로 실해하도록 예약하고 ScheduledFuture 반환
  • 이후에 주어진 주기로 실행
  • 시작 시간을 기준으로 일정한 간격으로 작업을 반복
  • command: 실행할 작업
  • initDelay: 첫 번째 실행을 지연할 시간
  • period: 연속적인 실행 사이의 주기
  • unit: initDelay와 period 매개변수의 시간 단위
  • 작업 시간이 period보다 클 경우 작업시간이 끝나고 실행되기 때문에 period가 의미 없을 수 있음
    • 따라서 가급적 period >= 작업 시간이 되도록 설정하는 것을 추천
    • ex) 작업 시간 2초, period 1초일 경우 작업이 끝나지 않아도 1초 만에 다시 실행될 것을 예상하지만 작업시간이 끝나고 즉 2초 후 실행되는 것을 확인할 수 있음


 

1.4 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

  • 초기 지연 시간 이후 Runnable 작업을 주기적으로 실해하도록 예약하고 ScheduledFuture 반환
  • 작업이 완료되고 나서 지연 시간 후 실행
  • 종료 시간을 기준으로 일정한 간격으로 작업을 반복
  • command: 실행할 작업
  • initDelay: 첫 번째 실행을 지연할 시간
  • delay: 연속적인 실행 사이의 지연 시간
  • unit: initDelay와 delay 매개변수의 시간 단위

 

2. ScheduledFuture

  • ScheduledExecutorService를 사용하여 작업을 예약한 결과
  • 주요 목적은 지연이나 주기적인 작업 실행을 위한 것
  • 결과를 처리하는 것이 주목적은 아님
    • future.get()을 통해 결과 확인 가능하나 주로 예약된 future 취소할 때 유용하게 쓰임
    • getDelay(TimeUnit unit)을 통해 작업이 실행되기까지 남은 지연 시간 확인 가능

 

Executors

  • Executors는 Executor, ExecutorService, ScheduledExecutorService, ThreadFactory 및 Callable 클래스를 위한 유틸리티 팩토리 클래스
  • 쓰레드 풀 및 작업 스케줄링에 대한 다양한 메서드와 팩토리 메서드를 제공
    • 복잡한 멀티 쓰레드 환경에서의 작업을 간단하게 다룰 수 있음

 

1.1 쓰레드 풀 생성 메서드

  • ExecutorService 인터페이스를 구현한 클래스를 생성하고 반환하는 메서드
  • ScheduledExecutorService 인터페이스를 구현한 클래스를 생성하고 반환하는 메서드
  • ThreadFactory를 생성하고 반환하는 메서드

 

Executors의 쓰레드 풀 생성 메서드

 

1. 고정 크기 쓰레드 풀 생성

 

1.1 고정 크기 쓰레드 풀 생성 메서드

  • static ExecutorService newFixedThreadPool(int nThreads)
  • static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

 

1.1.1 static ExecutorService newFixedThreadPool(int nThreads)

  • 메서드의 매개변수로 원하는 쓰레드 개수를 지정할 수 있으며 지정한 개수만큼 쓰레드가 생성되어 작업을 처리
  • 쓰레드 풀은 모든 쓰레드가 공유하는 대기열을 지니고 있으며 대기열은 무한한 크기의 대기열쓰레드가 가용 상태이면 대기 중인 작업을 처리
  • 모든 쓰레드가 활성 상태인 상태에서 작업이 추가되면 쓰레드가 사용 가능한 상태가 될 때까지 작업들은 대기열에서 대기
  • 쓰레드 풀이 종료하기 전에 어떤 쓰레드가 실패로 종료하게 되면 필요한 경우 새로운 쓰레드가 대신함
  • nThreads가 0 미만일 경우 IllegalArgumentException 발생

 

1.1.2 static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

  • 1.2.1과 기능은 동일
  • 매개변수 ThreadFactory를 통해 쓰레드 생성과 관련된 로직 설정 가능
    • 커스텀하게 쓰레드 생성 방식 적용, 쓰레드 이름, 우선순위 등을 설정 가능

 

1.1.2.1 ThreadFactory

  • 쓰레드 생성과 관련된 세부 사항을 추상화하고 원하는 방식으로 쓰레드를 커스터마이징 할 수 있도록 지원하는 객체
  • ThreadFactory를 사용하면 스레드를 직접 생성할 필요 없이 스레드 하위 클래스나 우선순위 등을 설정할 수 있음
  • Executors.defaultThreadFactory 메서드는 기본적인 간단한 구현을 제공

 

 

2. 유동 및 단일 크기 쓰레드 풀 생성

 

2.1 유동 크기 쓰레드 풀 생성 메서드

  • static ExecutorService newCachedThreadPool()
  • static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

 

2.1.1 static ExecutorService newCachedThreadPool()

  • 작업이 제출되면 현재 사용 가능한 쓰레드가 있는지 확인하고 없으면 새 쓰레드를 생성하여 작업을 수행
  • 일반적으로 많은 수의 짧은 작업들을 병렬로 실행하면서 처리 성능을 향상할 수 있음
  • 60초 동안 사용되지 않은 쓰레드는 자동 종료되고 캐시에서 제거
  • 쓰레드의 개수를 제한하지 않으며 작업 요청이 많을 때는 쓰레드 수가 증가하고 작업 요청이 감소하면 유휴 상태의 쓰레드가 종료되어 쓰레드 풀의 쓰레드 개수 조절 (auto-scaling)
  • 작업을 담아놓고 대기시키는 blockingQueue가 아닌 쓰레드 간 작업을 주고 받는 동기 큐를 사용
    • 작업이 제출되면 해당 작업 즉시 실행

 

2.1.2 static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

  • 2.1.1과 기능은 동일
  • 매개변수 ThreadFactory를 통해 쓰레드 생성과 관련된 로직 설정 가능

 

 

부연 설명

  • 작업 개수만큼 쓰레드 개수가 늘어난 것을 확인 가능

 

2.2 단일 크기 쓰레드 풀 생성 메서드

  • static ExecutorService newSignleThreadExecutor()
  • static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

 

2.2.1 static ExecutorService newSignleThreadExecutor()

  • 단일 쓰레드를 사용하여 작업을 실행하는 ExecutorService
  • 작업들이 순차적으로 실행되는 것을 보장
  • ㅅ한 개의 쓰레드가 실행 중에 실패로 인해 종료될 경우 새로운 쓰레드가 생성되어 실패 이후 후속 작업을 대신 실행
  • 작업을 순차적으로 실행해야 하거나 여러 작업 간의 순서를 보장해야 할 때 유용
  • 쓰레드가 한 개이기 때문에 공유 자원 관리에 대한 복잡성 줄어듦
  • newFixedThreadPool(1)과 동등한 기능을 가짐

 

2.2.2 static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

  • 2.2.1과 기능은 동일
  • 매개변수 ThreadFactory를 통해 쓰레드 생성과 관련된 로직 설정 가능

 

3. 스케줄링 쓰레드 풀 생성

 

3.1 스케줄링 기능을 지원하는 쓰레드 풀 생성

  • static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
  • static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

 

3.1.1 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

  • 주어진 지연 시간 후에 명령을 실행하거나 주기적으로 실행할 수 있는 쓰레드 풀 생성
  • 한 개의 쓰레드가 실행 중에 실패로 인해 종료될 경우 새로운 쓰레드가 생성되어 실패 이후 후속 작업을 대신 실행
  • corePoolSize가 0보다 작거나 같을 경우 illegalArgumentException 예외 발생

 

3.1.2 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

  • 3.1.1과 기능은 동일
  • 매개변수 ThreadFactory를 통해 쓰레드 생성과 관련된 로직 설정 가능

 

3.2 스케줄링 기능을 지원하는 단일 쓰레드 풀 생성

  • static ScheduledExecutorService newSingleThreadScheduledExecutor()
  • static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)

 

3.2.1 static ScheduledExecutorService newSingleThreadScheduledExecutor()

  • 주어진 지연 후에 명령을 실행하거나 주기적으로 실행할 수 있는 단일 쓰레드 풀 생성
  • 한 개의 쓰레드가 실행 중에 실패로 인해 종료되었고 종료 이후에도 필요한 경우 새로운 쓰레드가 생성되어 후속 작업 실행
  • 작업은 순차적으로 실행되며 항상 하나의 작업만 활성화
  • newScheduledThreadPool(1)과 동등한 기능을 가짐

 

3.2.2 static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)

  • 3.2.1과 기능은 동일
  • 매개변수 ThreadFactory를 통해 쓰레드 생성과 관련된 로직 설정 가능

 

참고

자바 동시성 프로그래밍 [리액티브 프로그래밍 Part.1] - 정수원 강사님

반응형