Java 8 引入的 CompletableFuture
类,通过其强大的功能和灵活的接口,为我们提供了处理异步编程和并行计算的有力工具。本文将深入探讨 CompletableFuture
的设计精妙之处,并详细介绍其在实际应用中的几个典型场景。
CompletableFuture 设计的精妙之处
1. 非阻塞异步计算
CompletableFuture
通过非阻塞的方式实现异步计算,这意味着主线程不会因为等待异步任务的完成而被阻塞。
java
CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
return "Result";
});
上述代码中,supplyAsync
方法会在一个独立的线程中执行耗时操作,主线程可以继续执行其它任务。
java
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
private static <U> CompletableFuture<U> asyncSupplyStage(Executor e, Supplier<U> f) {
if (f == null) throw new NullPointerException();
CompletableFuture<U> d = new CompletableFuture<U>();
e.execute(new AsyncSupply<U>(d, f));
return d;
}
可以看到,supplyAsync
方法通过在一个独立的线程池(asyncPool
)中执行给定的 Supplier
,实现了非阻塞的异步计算。这里的 Executor
接口使得线程池的实现可以灵活配置。
2. 链式任务编排
CompletableFuture
提供了丰富的链式任务编排方法,如 thenApply
、thenAccept
、thenRun
等,使得多个异步任务可以串联起来组成一个完整的计算流程。
java
CompletableFuture.supplyAsync(() -> "Step1")
.thenApply(result -> "Step2: " + result)
.thenAccept(result -> System.out.println(result));
上述代码中,多个异步任务通过链式调用被串联起来,形成一个完整的计算流程。
java
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) {
return uniApplyStage(null, fn);
}
private <U> CompletableFuture<U> uniApplyStage(Executor e, Function<? super T, ? extends U> f) {
if (f == null) throw new NullPointerException();
CompletableFuture<U> d = new CompletableFuture<U>();
if (e != null || !d.uniApply(this, f, null)) {
UniApply<T,U> c = new UniApply<T,U>(e, d, this, f);
push(c);
c.tryFire(SYNC);
}
return d;
}
thenApply
方法通过内部的 uniApplyStage
方法实现了链式调用。可以看到,新的 CompletableFuture
被创建并返回,形成了链条的一部分。
3. 灵活的异常处理机制
CompletableFuture
提供了灵活的异常处理机制,如 exceptionally
、handle
等方法,可以在出现异常时进行处理。
java
CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("Failed");
}
return "Success";
}).exceptionally(ex -> "Recovered").thenAccept(System.out::println);
上述代码中,如果异步任务抛出异常,会通过 exceptionally
方法进行处理,并返回一个默认值。
java
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) {
return uniExceptionallyStage(null, fn);
}
private CompletableFuture<T> uniExceptionallyStage(Executor e, Function<Throwable, ? extends T> f) {
if (f == null) throw new NullPointerException();
CompletableFuture<T> d = new CompletableFuture<T>();
if (e != null || !d.uniExceptionally(this, f, null)) {
UniExceptionally<T> c = new UniExceptionally<T>(e, d, this, f);
push(c);
c.tryFire(SYNC);
}
return d;
}
exceptionally
方法通过内部的 uniExceptionallyStage
方法实现了异常处理。新的 CompletableFuture
被创建并返回,用于处理异常情况。
4. 并行任务的组合
CompletableFuture
提供了多种方法用于组合并行任务,如 thenCombine
、allOf
、anyOf
等,可以将多个异步任务的结果进行组合。
java
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Result1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Result2");
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + " & " + result2);
combinedFuture.thenAccept(System.out::println);
上述代码中,两个并行任务的结果通过 thenCombine
方法进行组合,并在最终输出。
java
public <U, V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) {
return biApplyStage(null, other, fn);
}
private <U, V> CompletableFuture<V> biApplyStage(Executor e, CompletionStage<? extends U> o, BiFunction<? super T, ? super U, ? extends V> f) {
if (f == null) throw new NullPointerException();
CompletableFuture<U> b = o.toCompletableFuture();
CompletableFuture<V> d = new CompletableFuture<V>();
if (e != null || !d.biApply(this, b, f, null)) {
BiApply<T, U, V> c = new BiApply<T, U, V>(e, d, this, b, f);
push(c);
b.push(c);
c.tryFire(SYNC);
}
return d;
}
thenCombine
方法通过内部的 biApplyStage
方法实现了并行任务的组合。可以看到,两个 CompletableFuture
被组合成一个新的 CompletableFuture
,形成了任务的组合。
5. 函数式编程
CompletableFuture
大量使用了 Java 8 引入的函数式编程接口(如 Function
、Consumer
、BiFunction
等),使得代码更加简洁和易于理解。
java
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(String::toUpperCase)
.thenAccept(System.out::println);
上述代码中,函数式接口 Function
和 Consumer
被用于链式调用,使得代码更加简洁。
CompletableFuture 的应用场景
1. 并行任务处理
在现代应用中,很多任务可以并行执行以提高效率。例如,从多个数据源获取数据、并行处理多个请求等。在这种场景下,CompletableFuture
可以显著提高系统的吞吐量和响应速度。
示例:并行获取数据
假设我们需要从两个独立的服务获取数据,并将结果合并。
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class ParallelDataFetch {
public static CompletableFuture<String> fetchDataFromService1() {
return CompletableFuture.supplyAsync(() -> {
try {
// 模拟耗时操作
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data from Service 1";
});
}
public static CompletableFuture<String> fetchDataFromService2() {
return CompletableFuture.supplyAsync(() -> {
try {
// 模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data from Service 2";
});
}
public static void main(String[] args) {
CompletableFuture<String> future1 = fetchDataFromService1();
CompletableFuture<String> future2 = fetchDataFromService2();
// 使用 thenCombine 将两个异步任务的结果组合起来
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (data1, data2) -> {
return data1 + " & " + data2;
});
try {
// 获取最终结果
String result = combinedFuture.get();
System.out.println("Combined Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
2. 异步请求处理
在 Web 应用中,我们常常需要处理大量的异步请求,比如异步读取数据库、调用外部 API 等。CompletableFuture
可以帮助我们在不阻塞主线程的情况下处理这些异步请求。
示例:异步处理HTTP请求
假设我们需要向两个不同的API发送HTTP请求,并将结果合并。
java
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
public class AsyncHttpRequest {
private static HttpClient client = HttpClient.newHttpClient();
public static CompletableFuture<String> sendRequest1() {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data1"))
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
public static CompletableFuture<String> sendRequest2() {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data2"))
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
public static void main(String[] args) {
CompletableFuture<String> future1 = sendRequest1();
CompletableFuture<String> future2 = sendRequest2();
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (data1, data2) -> {
return "Combined Data: " + data1 + " & " + data2;
});
combinedFuture.thenAccept(System.out::println);
// 阻塞主线程,等待异步任务完成
combinedFuture.join();
}
}
3. 异常处理
在异步编程中,异常处理一直是一个难点。CompletableFuture
提供了灵活的异常处理机制,使得我们可以优雅地处理异步任务中的异常。
示例:异步任务异常处理
假设我们有一个异步任务,它可能会抛出异常,我们希望在异常发生时提供一个默认值。
java
import java.util.concurrent.CompletableFuture;
public class AsyncExceptionHandling {
public static CompletableFuture<String> asyncTask() {
return CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("Something went wrong");
}
return "Success";
});
}
public static void main(String[] args) {
CompletableFuture<String> future = asyncTask();
future.exceptionally(ex -> {
System.out.println("Exception: " + ex.getMessage());
return "Default Value";
}).thenAccept(System.out::println);
// 阻塞主线程,等待异步任务完成
future.join();
}
}
4. 任务流水线
在某些场景下,我们需要将多个异步任务串联起来,形成一个任务流水线。CompletableFuture
提供了丰富的链式调用方法,使得我们可以轻松实现任务的流水线处理。
示例:任务流水线
假设我们有三个任务,它们需要依次执行,并且每个任务的输出是下一个任务的输入。
java
import java.util.concurrent.CompletableFuture;
public class TaskPipeline {
public static CompletableFuture<String> task1() {
return CompletableFuture.supplyAsync(() -> "Task 1 Result");
}
public static CompletableFuture<String> task2(String input) {
return CompletableFuture.supplyAsync(() -> input + " -> Task 2 Result");
}
public static CompletableFuture<String> task3(String input) {
return CompletableFuture.supplyAsync(() -> input + " -> Task 3 Result");
}
public static void main(String[] args) {
CompletableFuture<String> pipeline = task1()
.thenCompose(result1 -> task2(result1))
.thenCompose(result2 -> task3(result2));
pipeline.thenAccept(System.out::println);
// 阻塞主线程,等待异步任务完成
pipeline.join();
}
}
5. 响应式编程
在响应式编程中,我们需要对事件进行实时响应。CompletableFuture
可以帮助我们实现响应式编程,确保系统能够实时响应用户请求。
示例:响应式编程
假设我们有一个用户注册系统,需要在用户注册成功后发送欢迎邮件和更新用户统计信息。
java
import java.util.concurrent.CompletableFuture;
public class ReactiveProgramming {
public static CompletableFuture<Void> registerUser(String username) {
return CompletableFuture.runAsync(() -> {
// 模拟用户注册
System.out.println("User registered: " + username);
});
}
public static CompletableFuture<Void> sendWelcomeEmail(String username) {
return CompletableFuture.runAsync(() -> {
// 模拟发送欢迎邮件
System.out.println("Welcome email sent to: " + username);
});
}
public static CompletableFuture<Void> updateUserStats(String username) {
return CompletableFuture.runAsync(() -> {
// 模拟更新用户统计信息
System.out.println("User stats updated for: " + username);
});
}
public static void main(String[] args) {
String username = "john_doe";
CompletableFuture<Void> future = registerUser(username)
.thenCompose(result -> sendWelcomeEmail(username))
.thenCompose(result -> updateUserStats(username));
// 阻塞主线程,等待异步任务完成
future.join();
}
}
总结
CompletableFuture
在以下几个场景中非常有用:
- 并行任务处理:例如,从多个数据源获取数据、并行处理多个请求等。
- 异步请求处理:例如,异步读取数据库、调用外部 API 等。
- 异常处理:提供灵活的异常处理机制,确保异步任务的健壮性。
- 任务流水线:将多个异步任务串联起来,形成任务流水线。
- 响应式编程:对事件进行实时响应,确保系统能够实时响应用户请求。
通过上述示例和分析,可以看出 CompletableFuture
在实际应用中的强大功能和广泛应用场景。在实际开发中,合理使用 CompletableFuture
可以显著提高系统的性能和响应速度,提升代码的可读性和维护性。