226 lines
9.2 KiB
Markdown
226 lines
9.2 KiB
Markdown
### `CompletableFuture`에 대한 설명
|
|
|
|
`CompletableFuture`는 자바의 `java.util.concurrent` 패키지에 포함된 클래스입니다. 비동기 작업을 처리하고 결과를 조합하며, 예외 처리를 유연하게 다룰 수 있는 강력한 도구입니다. `Future` 인터페이스를 확장하여 논블록킹(non-blocking) 방식으로 작업을 연결하고, 작업 완료 시 후속 작업을 정의할 수 있습니다. 아래에서 주요 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다.
|
|
|
|
---
|
|
|
|
### `CompletableFuture` 주요 메서드 표
|
|
|
|
| 메서드명 | 반환 타입 | 설명 |
|
|
|---------------------------------------|----------------------------|----------------------------------------------------------------------------------------|
|
|
| `runAsync(Runnable)` | `CompletableFuture<Void>` | 지정된 `Runnable`을 비동기적으로 실행 |
|
|
| `supplyAsync(Supplier<T>)` | `CompletableFuture<T>` | 값을 제공하는 `Supplier`를 비동기적으로 실행하고 결과 반환 |
|
|
| `thenApply(Function<T, U>)` | `CompletableFuture<U>` | 이전 작업의 결과에 함수를 적용하여 변환된 결과 반환 |
|
|
| `thenAccept(Consumer<T>)` | `CompletableFuture<Void>` | 이전 작업의 결과를 소비(사용)하는 동작 수행 |
|
|
| `thenRun(Runnable)` | `CompletableFuture<Void>` | 이전 작업 완료 후 결과와 관계없이 실행할 작업 정의 |
|
|
| `thenCompose(Function<T, CompletableFuture<U>>)` | `CompletableFuture<U>` | 이전 결과에 따라 새로운 `CompletableFuture`를 반환 |
|
|
| `exceptionally(Function<Throwable, T>)` | `CompletableFuture<T>` | 예외 발생 시 복구 로직을 정의 |
|
|
| `handle(BiFunction<T, Throwable, U>)` | `CompletableFuture<U>` | 결과와 예외를 모두 처리하는 로직 정의 |
|
|
| `complete(T)` | `boolean` | 작업을 수동으로 완료하고 결과 설정 |
|
|
| `completeExceptionally(Throwable)` | `boolean` | 작업을 예외와 함께 수동으로 완료 |
|
|
| `join()` | `T` | 작업 완료를 기다리고 결과 반환 (예외 발생 시 던짐) |
|
|
| `get()` | `T` | `Future`처럼 결과를 기다리고 반환 (예외 처리 필요) |
|
|
| `allOf(CompletableFuture<?>...)` | `CompletableFuture<Void>` | 여러 `CompletableFuture`가 모두 완료될 때까지 대기 |
|
|
| `anyOf(CompletableFuture<?>...)` | `CompletableFuture<Object>`| 여러 `CompletableFuture` 중 하나가 완료되면 결과 반환 |
|
|
| `whenComplete(BiConsumer<T, Throwable>)` | `CompletableFuture<T>` | 작업 완료 시 결과와 예외를 처리 |
|
|
|
|
---
|
|
|
|
### `CompletableFuture` 설명 및 예시
|
|
|
|
`CompletableFuture`는 비동기 프로그래밍을 단순화하며, 작업 간 의존성을 표현하고 예외를 처리하는 데 유용합니다. 기본적으로 `ForkJoinPool.commonPool()`을 사용하지만, 사용자 지정 `Executor`를 전달할 수도 있습니다.
|
|
|
|
#### 1. 기본 비동기 작업: `supplyAsync`
|
|
비동기적으로 값을 생성합니다.
|
|
|
|
```java
|
|
import java.util.concurrent.*;
|
|
|
|
public class SupplyAsyncExample {
|
|
public static void main(String[] args) throws Exception {
|
|
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace();
|
|
}
|
|
return "비동기 작업 결과";
|
|
});
|
|
|
|
System.out.println("작업 시작");
|
|
String result = future.get();
|
|
System.out.println(result);
|
|
}
|
|
}
|
|
```
|
|
- **출력 예시**:
|
|
```
|
|
작업 시작
|
|
(1초 후)
|
|
비동기 작업 결과
|
|
```
|
|
|
|
#### 2. 결과 변환: `thenApply`
|
|
이전 작업의 결과를 변환합니다.
|
|
|
|
```java
|
|
import java.util.concurrent.*;
|
|
|
|
public class ThenApplyExample {
|
|
public static void main(String[] args) throws Exception {
|
|
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
|
|
.thenApply(s -> s + " World")
|
|
.thenApply(String::toUpperCase);
|
|
|
|
String result = future.get();
|
|
System.out.println(result);
|
|
}
|
|
}
|
|
```
|
|
- **출력 예시**:
|
|
```
|
|
HELLO WORLD
|
|
```
|
|
- **설명**: `thenApply`로 결과에 연속적인 변환을 적용.
|
|
|
|
#### 3. 결과 소비: `thenAccept`
|
|
결과를 소비하고 후속 작업을 정의합니다.
|
|
|
|
```java
|
|
import java.util.concurrent.*;
|
|
|
|
public class ThenAcceptExample {
|
|
public static void main(String[] args) throws Exception {
|
|
CompletableFuture.supplyAsync(() -> "데이터 처리")
|
|
.thenAccept(result -> System.out.println("결과: " + result))
|
|
.thenRun(() -> System.out.println("작업 완료"));
|
|
|
|
Thread.sleep(1000); // 비동기 작업 완료 대기
|
|
}
|
|
}
|
|
```
|
|
- **출력 예시**:
|
|
```
|
|
결과: 데이터 처리
|
|
작업 완료
|
|
```
|
|
|
|
#### 4. 예외 처리: `exceptionally`
|
|
예외 발생 시 복구 로직을 정의합니다.
|
|
|
|
```java
|
|
import java.util.concurrent.*;
|
|
|
|
public class ExceptionallyExample {
|
|
public static void main(String[] args) throws Exception {
|
|
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
|
|
if (true) throw new RuntimeException("에러 발생");
|
|
return "성공";
|
|
}).exceptionally(throwable -> "복구: " + throwable.getMessage());
|
|
|
|
String result = future.get();
|
|
System.out.println(result);
|
|
}
|
|
}
|
|
```
|
|
- **출력 예시**:
|
|
```
|
|
복구: java.lang.RuntimeException: 에러 발생
|
|
```
|
|
|
|
#### 5. 작업 조합: `thenCompose`
|
|
이전 결과에 따라 새로운 `CompletableFuture`를 연결합니다.
|
|
|
|
```java
|
|
import java.util.concurrent.*;
|
|
|
|
public class ThenComposeExample {
|
|
public static void main(String[] args) throws Exception {
|
|
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "첫 번째")
|
|
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " 두 번째"));
|
|
|
|
String result = future.get();
|
|
System.out.println(result);
|
|
}
|
|
}
|
|
```
|
|
- **출력 예시**:
|
|
```
|
|
첫 번째 두 번째
|
|
```
|
|
|
|
#### 6. 여러 작업 조합: `allOf`
|
|
여러 작업의 완료를 기다립니다.
|
|
|
|
```java
|
|
import java.util.concurrent.*;
|
|
|
|
public class AllOfExample {
|
|
public static void main(String[] args) throws Exception {
|
|
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
|
|
try { Thread.sleep(1000); } catch (Exception e) {}
|
|
return "작업 1";
|
|
});
|
|
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "작업 2");
|
|
|
|
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
|
|
all.thenRun(() -> {
|
|
try {
|
|
System.out.println(future1.get() + " & " + future2.get());
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}).join();
|
|
}
|
|
}
|
|
```
|
|
- **출력 예시**:
|
|
```
|
|
작업 1 & 작업 2
|
|
```
|
|
|
|
#### 7. 수동 완료: `complete`
|
|
작업을 외부에서 수동으로 완료합니다.
|
|
|
|
```java
|
|
import java.util.concurrent.*;
|
|
|
|
public class CompleteExample {
|
|
public static void main(String[] args) {
|
|
CompletableFuture<String> future = new CompletableFuture<>();
|
|
|
|
new Thread(() -> {
|
|
try {
|
|
Thread.sleep(1000);
|
|
future.complete("수동 완료");
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}).start();
|
|
|
|
String result = future.join();
|
|
System.out.println(result);
|
|
}
|
|
}
|
|
```
|
|
- **출력 예시**:
|
|
```
|
|
(1초 후)
|
|
수동 완료
|
|
```
|
|
|
|
---
|
|
|
|
### `CompletableFuture`의 장점과 주의점
|
|
- **장점**:
|
|
- 비동기 작업의 연속적 처리와 조합 가능.
|
|
- 예외 처리가 명시적이고 유연.
|
|
- 논블록킹 방식으로 작업 연결 가능.
|
|
- **주의점**:
|
|
- `get()`이나 `join()`을 과도히 사용하면 블록킹으로 인해 비동기 이점이 줄어듦.
|
|
- 복잡한 조합 시 코드 가독성 관리 필요.
|
|
|
|
---
|
|
|
|
### 결론
|
|
`CompletableFuture`는 비동기 작업을 정의하고, 결과를 변환하며, 예외를 처리하는 데 탁월합니다. `supplyAsync`, `thenApply`, `exceptionally`, `allOf` 등 다양한 메서드를 활용하면 작업 흐름을 효율적으로 설계할 수 있습니다. 위 예시를 참고하여 실제 애플리케이션에서 비동기 프로그래밍을 구현해 보세요! |