401 lines
17 KiB
Markdown
401 lines
17 KiB
Markdown
### 자바의 `Executor`와 관련된 설명
|
|
|
|
자바의 `Executor` 프레임워크는 스레드 풀을 관리하고 작업을 비동기적으로 실행하기 위한 강력한 도구로, `java.util.concurrent` 패키지에 포함되어 있습니다. `Executor` 인터페이스를 기반으로, 스레드 생성과 관리를 직접 다루는 대신 작업 단위로 실행을 위임하여 코드의 가독성과 효율성을 높입니다. 주요 클래스와 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다.
|
|
|
|
---
|
|
|
|
### `Executor`와 관련된 클래스 및 메서드 표
|
|
|
|
#### `Executor` 인터페이스
|
|
| 메서드명 | 반환 타입 | 설명 |
|
|
|-------------------------|-----------|---------------------------------------------------|
|
|
| `execute(Runnable)` | `void` | `Runnable` 작업을 실행하도록 스레드 풀에 위임 |
|
|
|
|
#### `ExecutorService` 인터페이스 (확장 인터페이스)
|
|
| 메서드명 | 반환 타입 | 설명 |
|
|
|-----------------------------------|-------------------|----------------------------------------------------------------------|
|
|
| `submit(Runnable)` | `Future<?>` | `Runnable` 작업을 실행하고 결과를 추적할 수 있는 `Future` 반환 |
|
|
| `submit(Callable<T>)` | `Future<T>` | `Callable` 작업을 실행하고 결과를 반환하는 `Future` 반환 |
|
|
| `shutdown()` | `void` | 새로운 작업을 받지 않고, 기존 작업 완료 후 종료 |
|
|
| `shutdownNow()` | `List<Runnable>` | 즉시 종료 시도하고 실행 대기 중인 작업 목록 반환 |
|
|
| `awaitTermination(long, TimeUnit)` | `boolean` | 지정된 시간 동안 종료를 기다리며, 완료 여부 반환 |
|
|
| `invokeAll(Collection<Callable<T>>)` | `List<Future<T>>` | 모든 `Callable` 작업을 실행하고 결과 목록 반환 |
|
|
| `invokeAny(Collection<Callable<T>>)` | `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<T>` | 비동기 작업의 결과를 추적하거나 취소할 수 있는 인터페이스 |
|
|
| `Callable<T>` | `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<Integer> task = () -> {
|
|
Thread.sleep(1000);
|
|
return 42;
|
|
};
|
|
|
|
Future<Integer> 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<Callable<String>> tasks = Arrays.asList(
|
|
() -> { Thread.sleep(1000); return "작업 1 완료"; },
|
|
() -> { Thread.sleep(500); return "작업 2 완료"; },
|
|
() -> { Thread.sleep(1500); return "작업 3 완료"; }
|
|
);
|
|
|
|
List<Future<String>> futures = executor.invokeAll(tasks);
|
|
|
|
for (Future<String> 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<Void> 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`는 주기적 스케줄링보다는 비동기 작업 조합에 강점을 가집니다. 요구사항에 따라 적절한 도구를 선택해 사용하세요! |