### 자바의 `Executor`와 관련된 설명 자바의 `Executor` 프레임워크는 스레드 풀을 관리하고 작업을 비동기적으로 실행하기 위한 강력한 도구로, `java.util.concurrent` 패키지에 포함되어 있습니다. `Executor` 인터페이스를 기반으로, 스레드 생성과 관리를 직접 다루는 대신 작업 단위로 실행을 위임하여 코드의 가독성과 효율성을 높입니다. 주요 클래스와 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다. --- ### `Executor`와 관련된 클래스 및 메서드 표 #### `Executor` 인터페이스 | 메서드명 | 반환 타입 | 설명 | |-------------------------|-----------|---------------------------------------------------| | `execute(Runnable)` | `void` | `Runnable` 작업을 실행하도록 스레드 풀에 위임 | #### `ExecutorService` 인터페이스 (확장 인터페이스) | 메서드명 | 반환 타입 | 설명 | |-----------------------------------|-------------------|----------------------------------------------------------------------| | `submit(Runnable)` | `Future` | `Runnable` 작업을 실행하고 결과를 추적할 수 있는 `Future` 반환 | | `submit(Callable)` | `Future` | `Callable` 작업을 실행하고 결과를 반환하는 `Future` 반환 | | `shutdown()` | `void` | 새로운 작업을 받지 않고, 기존 작업 완료 후 종료 | | `shutdownNow()` | `List` | 즉시 종료 시도하고 실행 대기 중인 작업 목록 반환 | | `awaitTermination(long, TimeUnit)` | `boolean` | 지정된 시간 동안 종료를 기다리며, 완료 여부 반환 | | `invokeAll(Collection>)` | `List>` | 모든 `Callable` 작업을 실행하고 결과 목록 반환 | | `invokeAny(Collection>)` | `T` | 주어진 `Callable` 중 하나가 완료되면 그 결과를 반환 | #### `Executors` 유틸리티 클래스 (정적 팩토리 메서드) | 메서드명 | 반환 타입 | 설명 | |-----------------------------------|-------------------|----------------------------------------------------------------------| | `newFixedThreadPool(int n)` | `ExecutorService` | 고정 크기의 스레드 풀 생성 | | `newSingleThreadExecutor()` | `ExecutorService` | 단일 스레드로 작업을 순차적으로 실행하는 풀 생성 | | `newCachedThreadPool()` | `ExecutorService` | 필요에 따라 스레드를 생성/재사용하는 동적 풀 생성 | | `newScheduledThreadPool(int n)` | `ScheduledExecutorService` | 주기적 작업을 스케줄링할 수 있는 스레드 풀 생성 | #### `ScheduledExecutorService` 인터페이스 (확장 인터페이스) | 메서드명 | 반환 타입 | 설명 | |-----------------------------------|-------------------|----------------------------------------------------------------------| | `schedule(Runnable, long, TimeUnit)` | `ScheduledFuture` | 지정된 지연 시간 후 작업 실행 | | `scheduleAtFixedRate(Runnable, long, long, TimeUnit)` | `ScheduledFuture` | 고정 주기로 작업 반복 실행 | | `scheduleWithFixedDelay(Runnable, long, long, TimeUnit)` | `ScheduledFuture` | 작업 완료 후 고정 지연을 두고 반복 실행 | #### 관련 클래스 | 클래스명 | 설명 | |-------------------------|----------------------------------------------------------------------| | `ThreadPoolExecutor` | 스레드 풀의 세부 설정(코어 풀 크기, 최대 풀 크기 등)을 커스터마이징 가능 | | `Future` | 비동기 작업의 결과를 추적하거나 취소할 수 있는 인터페이스 | | `Callable` | `Runnable`과 유사하나 결과를 반환하며 예외를 던질 수 있는 인터페이스 | --- ### `Executor` 설명 및 예시 `Executor` 프레임워크는 스레드 관리의 복잡성을 줄이고, 작업 실행을 추상화하여 재사용 가능한 스레드 풀을 제공합니다. 아래에서 주요 사용 사례를 예시로 설명합니다. #### 1. 기본 사용: `ExecutorService`와 `newFixedThreadPool` 고정 크기의 스레드 풀을 생성하고 작업을 실행합니다. ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FixedThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); // 2개 스레드 풀 for (int i = 0; i < 5; i++) { int taskId = i; executor.execute(() -> { System.out.println("작업 " + taskId + " 실행 중: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); // 작업 제출 후 종료 요청 } } ``` - **출력 예시**: ``` 작업 0 실행 중: pool-1-thread-1 작업 1 실행 중: pool-1-thread-2 (1초 후) 작업 2 실행 중: pool-1-thread-1 작업 3 실행 중: pool-1-thread-2 (1초 후) 작업 4 실행 중: pool-1-thread-1 ``` - **설명**: 2개의 스레드가 최대 2개 작업을 동시에 처리하며, 나머지는 대기 후 실행됨. #### 2. `Callable`과 `Future`로 결과 받기 `submit`을 사용해 결과를 반환하는 작업을 실행합니다. ```java import java.util.concurrent.*; public class CallableExample { public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor(); Callable task = () -> { Thread.sleep(1000); return 42; }; Future future = executor.submit(task); System.out.println("작업 제출 완료"); Integer result = future.get(); // 작업 완료까지 대기 후 결과 가져오기 System.out.println("결과: " + result); executor.shutdown(); } } ``` - **출력 예시**: ``` 작업 제출 완료 (1초 후) 결과: 42 ``` - **설명**: `Future.get()`은 작업이 완료될 때까지 블록하며, 결과를 반환받음. #### 3. 주기적 실행: `ScheduledExecutorService` `scheduleAtFixedRate`로 주기적으로 작업을 실행합니다. ```java import java.util.concurrent.*; public class ScheduledExecutorExample { public static void main(String[] args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); Runnable task = () -> System.out.println("작업 실행: " + System.currentTimeMillis()); executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS); // 1초 후 시작, 2초 주기 try { Thread.sleep(5000); // 5초 동안 실행 } catch (InterruptedException e) { e.printStackTrace(); } executor.shutdown(); } } ``` - **출력 예시**: ``` (1초 후) 작업 실행: 1678321234567 (2초 후) 작업 실행: 1678321236567 (2초 후) 작업 실행: 1678321238567 ``` - **설명**: 작업이 고정된 2초 주기로 실행됨. #### 4. `invokeAll`로 여러 작업 실행 여러 `Callable` 작업을 한 번에 실행하고 결과를 수집합니다. ```java import java.util.concurrent.*; import java.util.Arrays; import java.util.List; public class InvokeAllExample { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newFixedThreadPool(2); List> tasks = Arrays.asList( () -> { Thread.sleep(1000); return "작업 1 완료"; }, () -> { Thread.sleep(500); return "작업 2 완료"; }, () -> { Thread.sleep(1500); return "작업 3 완료"; } ); List> futures = executor.invokeAll(tasks); for (Future future : futures) { System.out.println(future.get()); } executor.shutdown(); } } ``` - **출력 예시**: ``` 작업 1 완료 작업 2 완료 작업 3 완료 ``` - **설명**: 모든 작업이 완료될 때까지 대기한 후 결과를 순차적으로 출력. --- ### `Executor`의 장점과 주의점 - **장점**: - 스레드 풀을 재사용하여 리소스 낭비 감소. - 작업 실행과 스레드 관리를 분리하여 코드 간소화. - 주기적 작업 및 결과 처리가 용이. - **주의점**: - `shutdown()`을 호출하지 않으면 프로그램이 종료되지 않을 수 있음. - 스레드 풀 크기와 작업 부하를 적절히 조정해야 성능 최적화 가능. --- ### 결론 `Executor` 프레임워크는 스레드 관리의 복잡성을 줄이고, 다양한 실행 패턴(단일 실행, 주기적 실행, 병렬 실행 등)을 지원합니다. `ExecutorService`, `ScheduledExecutorService`, `Executors`를 활용하면 효율적인 멀티스레드 애플리케이션을 쉽게 구현할 수 있습니다. 위 예시를 참고하여 실제 프로젝트에 적용해 보세요! --- ### `ScheduledExecutorService`와 다른 스케줄링 도구 비교 자바에서 주기적이거나 지연된 작업을 스케줄링할 때 `ScheduledExecutorService`는 강력한 도구로 사용됩니다. 하지만 `java.util.Timer`와 `CompletableFuture`와 같은 대안도 있으며, 각각의 특징과 사용 사례가 다릅니다. 아래에서 `ScheduledExecutorService`를 `Timer`와 `CompletableFuture`와 비교하여 표로 정리하고, 예시와 함께 설명하겠습니다. --- ### 비교 표 | **특징** | **`ScheduledExecutorService`** | **`Timer`** | **`CompletableFuture`** | |---------------------------|-----------------------------------------------------|------------------------------------------|------------------------------------------| | **패키지** | `java.util.concurrent` | `java.util` | `java.util.concurrent` | | **스레드 모델** | 스레드 풀 기반 (다중 스레드 지원) | 단일 스레드 기반 | 기본 풀(`ForkJoinPool`) 또는 사용자 지정 | | **작업 타입** | `Runnable`, `Callable` | `TimerTask` | `Runnable`, `Supplier` | | **스케줄링 메서드** | `schedule`, `scheduleAtFixedRate`, `scheduleWithFixedDelay` | `schedule`, `scheduleAtFixedRate` | `supplyAsync` + `then` 조합으로 간접 지원 | | **고정 주기 지원** | `scheduleAtFixedRate` (고정 주기), `scheduleWithFixedDelay` (고정 지연) | `scheduleAtFixedRate` (고정 주기) | 직접 지원 없음, 별도 로직 필요 | | **예외 처리** | 개별 작업 예외가 전체에 영향 없음 | 예외 발생 시 `Timer` 종료 가능 | `exceptionally` 등으로 명시적 처리 가능 | | **취소 기능** | `ScheduledFuture.cancel()` | `TimerTask.cancel()`, `Timer.cancel()` | `complete()`, `cancel()` | | **복잡한 작업 조합** | 제한적 | 없음 | `thenApply`, `allOf` 등으로 가능 | | **성능 및 확장성** | 높은 부하와 복잡한 작업에 적합 | 간단한 작업에 적합 | 비동기 작업 조합에 강력 | | **사용 용도** | 주기적 작업, 다중 스레드 스케줄링 | 간단한 단일 스레드 스케줄링 | 비동기 작업 흐름 관리 | --- ### 상세 설명 및 예시 #### 1. `ScheduledExecutorService` - **특징**: 스레드 풀을 활용하여 다중 스레드로 작업을 처리하며, 예외 발생 시 다른 작업에 영향을 주지 않습니다. - **예시**: 주기적으로 상태를 점검하는 작업. ```java import java.util.concurrent.*; public class ScheduledExecutorExample { public static void main(String[] args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); Runnable task = () -> System.out.println("실행: " + System.currentTimeMillis()); executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } executor.shutdown(); } } ``` - **출력 예시**: ``` 실행: 1678321234567 실행: 1678321236567 실행: 1678321238567 ``` #### 2. `Timer` - **특징**: 단일 스레드로 동작하며, 작업 중 예외가 발생하면 `Timer`가 종료될 수 있습니다. - **예시**: 간단한 알림 기능. ```java import java.util.*; public class TimerExample { public static void main(String[] args) { Timer timer = new Timer(); TimerTask task = new TimerTask() { @Override public void run() { System.out.println("실행: " + System.currentTimeMillis()); } }; timer.scheduleAtFixedRate(task, 1000, 2000); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } timer.cancel(); } } ``` - **출력 예시**: ``` 실행: 1678321234567 실행: 1678321236567 실행: 1678321238567 ``` #### 3. `CompletableFuture` - **특징**: 주기적 스케줄링은 직접 지원하지 않지만, 비동기 작업 조합과 예외 처리가 뛰어납니다. - **예시**: 작업 완료 후 후속 작업 실행. ```java import java.util.concurrent.*; public class CompletableFutureExample { public static void main(String[] args) throws Exception { CompletableFuture future = CompletableFuture.runAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("실행: " + System.currentTimeMillis()); }).thenRun(() -> System.out.println("후속 작업")); future.get(); } } ``` - **출력 예시**: ``` 실행: 1678321235567 후속 작업 ``` #### 비교 예시: 예외 발생 시 동작 - **`ScheduledExecutorService`**와 **`Timer`**의 차이점을 보여줍니다. ```java import java.util.*; import java.util.concurrent.*; public class ExceptionComparison { public static void main(String[] args) { // Timer 예시 Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { int count = 0; @Override public void run() { count++; System.out.println("Timer 실행 " + count); if (count == 2) throw new RuntimeException("Timer 예외"); } }, 0, 1000); // ScheduledExecutorService 예시 ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate(() -> { int count = 0; count++; System.out.println("Executor 실행 " + count); if (count == 2) throw new RuntimeException("Executor 예외"); }, 0, 1, TimeUnit.SECONDS); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } timer.cancel(); executor.shutdown(); } } ``` - **출력 예상**: ``` Timer 실행 1 Timer 실행 2 (예외 발생 후 Timer 종료, 더 이상 실행 안 됨) Executor 실행 1 Executor 실행 2 (예외 발생 후에도 다른 작업은 계속 실행 가능) ``` - **설명**: `Timer`는 예외로 종료되지만, `ScheduledExecutorService`는 영향을 받지 않음. --- ### 선택 가이드 - **`ScheduledExecutorService`**: - 다중 스레드와 안정성이 필요한 주기적 작업. - 복잡한 작업 관리와 확장성 요구 시. - **`Timer`**: - 간단한 단일 스레드 작업. - 경량 애플리케이션에서 최소한의 스케줄링 필요 시. - **`CompletableFuture`**: - 비동기 작업 흐름을 조합하거나 단일 작업의 후속 처리가 필요할 때. - 주기적 스케줄링보다는 결과 기반 작업에 적합. --- ### 결론 `ScheduledExecutorService`는 스레드 풀 기반으로 안정성과 유연성을 제공하며, `Timer`는 간단한 작업에 적합하지만 취약점이 있습니다. `CompletableFuture`는 주기적 스케줄링보다는 비동기 작업 조합에 강점을 가집니다. 요구사항에 따라 적절한 도구를 선택해 사용하세요!