9.2 KiB
9.2 KiB
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
비동기적으로 값을 생성합니다.
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
이전 작업의 결과를 변환합니다.
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
결과를 소비하고 후속 작업을 정의합니다.
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
예외 발생 시 복구 로직을 정의합니다.
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를 연결합니다.
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
여러 작업의 완료를 기다립니다.
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
작업을 외부에서 수동으로 완료합니다.
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 등 다양한 메서드를 활용하면 작업 흐름을 효율적으로 설계할 수 있습니다. 위 예시를 참고하여 실제 애플리케이션에서 비동기 프로그래밍을 구현해 보세요!