CompletableFuture
是 Java 8 引入的一个类,用于简化异步编程模型。它是 Future
接口的一个增强版本,提供了更加丰富的功能和更灵活的用法。CompletableFuture
允许开发者以一种声明式和链式的方式编写异步代码,这样可以提高代码的可读性和可维护性。
CompletableFuture
的定义
CompletableFuture
它同时实现了Future
和CompletionStage
两个接口。
- Future:
Future
接口代表了一个异步计算的结果,也就是说,它代表了某个未来可能完成的计算结果。Future接口提供了获取结果、检查是否完成、取消任务等方法。 - CompletionStage:
CompletionStage
接口则代表了一个异步计算的阶段,或者说,它代表了一个异步计算过程的某一步(Stage)。这一步可能是由另一个CompletionStage
触发的,也可能是在当前步骤完成后触发的。这就意味着,我们可以利用CompletionStage
接口来编排和组合多个异步计算步骤,从而实现复杂的异步编程逻辑。
为什么要使用 CompletableFuture
?
- 简化异步编程:在传统的异步编程中,处理线程间的通信和数据传递通常需要复杂的同步机制,如
wait
/notify
、CountDownLatch
、CyclicBarrier
等。CompletableFuture
提供了一系列的方法,如thenApply
、thenAccept
、thenRun
等,使得异步操作的编排变得更加简单直观。 - 支持非阻塞操作:
CompletableFuture
支持非阻塞的异步编程模式。开发者可以在不阻塞当前线程的情况下,安排一个异步操作,并在操作完成时接收通知。这种方式可以提高应用程序的响应性和吞吐量。 - 丰富的组合操作:
CompletableFuture
提供了多种方法来组合多个异步操作,例如thenCombine
、thenCompose
、allOf
、anyOf
等。这些方法可以帮助开发者轻松实现复杂的异步流程控制。 - 异常处理:
CompletableFuture
允许开发者通过exceptionally
和handle
方法来处理异步操作中发生的异常,这样可以避免异常导致的线程中断和应用程序崩溃。 - 支持取消操作:
CompletableFuture
支持取消异步操作。如果某个异步任务不再需要,可以通过cancel
方法来取消它,从而节省系统资源。 - 更好的并发控制:
CompletableFuture
可以与 Java 的其他并发工具(如ExecutorService
)无缝集成,提供了更好的并发控制能力。
CompletableFuture
使用场景
- IO密集型操作:如数据库操作、文件读写、网络请求等,可以使用
CompletableFuture
来异步执行,避免阻塞主线程。 - 计算密集型操作:如果计算可以在不同的线程中并行执行,使用
CompletableFuture
可以提高计算效率。 - 异步编程模式:在需要处理多个异步操作的结果,并且这些操作之间存在依赖关系时,
CompletableFuture
提供了一种优雅的解决方案。
CompletableFuture
如何使用
零依赖:CompletableFuture
的创建
CompletableFuture
的创建方式有很多中,下面将逐一进行介绍。
使用 new CompletableFuture()
构造函数
可以使用new CompletableFuture()
构造函数来创建一个新的CompletableFuture
,这时CompletableFuture
还没有任何结果,你需要手动调用complete
方法来设置CompletableFuture
来完成这个CompletableFuture
。例如,你希望创建一个CompletableFuture
,然后将其交给某个异步任务去完成。
CompletableFuture<String> future = new CompletableFuture<>();
// 现在future是未完成的
// 可以通过future.complete("result")来设置结果
// 或者通过future.completeExceptionally(new Exception())来设置异常
使用completedFuture()
方法
completedFuture()
方法是CompletableFuture
类的一个静态方法,它允许你立即创建一个已经完成的CompletableFuture
对象。这个方法通常用于创建一个已经包含结果的CompletableFuture
,而不需要等待异步任务完成。
// 创建一个已经完成的CompletableFuture
CompletableFuture<String> future = CompletableFuture.completedFuture("Hello");
// 获取结果
String result = future.get();
System.out.println(result); // 输出:Hello
使用supplyAsync()
方法
supplyAsync()
是CompletableFuture
类的一个静态方法,它允许你创建一个新的CompletableFuture
,这个CompletableFuture
会在后台线程中执行一个任务,并在任务完成后返回结果。
supplyAsync()
方法有两个重载版本,它们的区别在于是否提供了一个Executor
参数。
supplyAsync(Supplier<T> supplier)
:这个版本的supplyAsync()
方法会在一个由系统管理的线程池中执行任务。这意味着你无法控制任务的执行环境,例如线程池的大小或者线程的优先级。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
supplyAsync(Supplier<T> supplier, Executor executor)
:这个版本的supplyAsync()
方法允许你提供一个自定义的Executor
,这样你就可以控制任务的执行环境。
Executor executor = Executors.newSingleThreadExecutor();
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello", executor);
使用runAsync()
方法
runAsync()
方法是CompletableFuture
类的另一个静态方法,它允许你创建一个新的CompletableFuture
,这个CompletableFuture
会在后台线程中执行一个任务,但是这个任务不会有任何返回值。也就是说,runAsync()
方法主要用于执行一些没有返回值的任务,比如发送邮件、下载文件等等。
runAsync()
方法也有两个重载版本,它们的区别同样在于是否提供了一个Executor
参数。
runAsync(Runnable runnable)
:这个版本的runAsync()
方法会在一个由系统管理的线程池中执行任务。同样地,你无法控制任务的执行环境,例如线程池的大小或者线程的优先级。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 这里写你的任务代码
});
runAsync(Runnable runnable, Executor executor)
:这个版本的runAsync()
方法允许你提供一个自定义的Executor
,这样你就可以控制任务的执行环境。
Executor executor = Executors.newSingleThreadExecutor();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 这里写你的任务代码
}, executor);
CompletableFuture
创建方式总结
为什么要用自定义的线程池
使用自定义的线程池创建异步任务,主要原因是为了更好地控制和管理线程资源。以下是使用自定义线程池的一些优点:
- 更好的资源管理:自定义线程池可以根据应用的需求来调整线程的数量和类型,以满足不同的性能需求。例如,如果你的应用主要是I/O密集型的,那么你可能需要一个较大的线程池;如果你的应用主要是CPU密集型的,那么你可能需要一个较小的线程池。
- 避免过度创建线程:如果没有使用线程池,那么每次创建一个新线程都会消耗一定的系统资源。而使用线程池后,系统只需要创建一定数量的线程,然后在多个任务之间复用这些线程,从而避免了过度创建线程的问题。
- 提高系统稳定性:自定义线程池可以设置合适的线程数量和线程类型,从而提高系统的稳定性和可靠性。例如,你可以设置线程池的最大线程数为系统的可用内存除以线程的大小,这样可以防止系统因为过多的线程而导致内存溢出。
相比之下,使用默认的线程池有一些潜在的问题:
- 不适合I/O密集型任务:默认的线程池通常是针对CPU密集型任务设计的,对于I/O密集型任务可能会导致线程资源的浪费。当不传递Executor时,会使用
ForkJoinPool
中的共用线程池CommonPool
,CommonPool
的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈。 - 可能导致线程资源不足:如果系统中同时运行的任务太多,那么默认的线程池可能会导致线程资源不足,从而影响系统的性能。
- 难以进行优化:由于默认的线程池是由系统管理的,所以用户很难对其进行优化。例如,用户不能根据具体的任务需求来调整线程池的大小和类型。
因此,为了更好地控制和管理线程资源,通常建议使用自定义的线程池来创建异步任务。
CompletableFuture
获取返回结果
CompletableFuture
它提供了多种方法来处理异步操作的结果。以下是 CompletableFuture
中 get()
、join()
和 isDone()
方法的详细说明
get()的具体使用方法
get()
方法用于获取 CompletableFuture
的结果。如果异步操作尚未完成,get()
方法会阻塞当前线程直到操作完成。如果异步操作失败,get()
方法会抛出 ExecutionException
来包装原始的异常。如果当前线程在等待期间被中断,它会抛出 InterruptedException
。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 执行一些异步操作
return "Result";
});
try {
String result = future.get(); // 阻塞直到 future 完成
System.out.println(result);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置中断状态
// 处理中断异常
} catch (ExecutionException e) {
// 处理异步操作中的异常
}
join()的具体使用方法
join()
方法与 get()
方法类似,也用于获取 CompletableFuture
的结果。不同之处在于,join()
不会抛出 InterruptedException
。如果当前线程在等待期间被中断,join()
会将中断状态设置回线程,但不抛出异常。join()
可以视为 get()
的非抛出中断版本的替代方法。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 执行一些异步操作
System.out.println("Running some async operation...");
});
future.join(); // 阻塞直到 future 完成
isDone()的具体使用方法
isDone()
方法用于检查 CompletableFuture
是否已经完成。它返回一个布尔值,如果异步操作已经完成,则返回 true
;否则返回 false
。这个方法是非阻塞的,可以用来轮询异步操作的完成状态。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 执行一些异步操作
System.out.println("Running some async operation...");
});
boolean isCompleted = future.isDone(); // 检查 future 是否完成
if (isCompleted) {
// 处理已完成的操作
System.out.println("Async operation completed.");
} else {
// 处理未完成的操作
System.out.println("Async operation is still running.");
}
使用方式的区别
get()
和join()
都会等待异步操作完成并返回结果,但get()
在等待期间线程被中断时会抛出异常,而join()
不会。isDone()
用于检查异步操作是否完成,而不等待操作完成,适用于非阻塞的轮询场景。
在实际使用中,选择哪个方法取决于你的具体需求,例如是否需要处理中断、是否需要立即结果以及是否希望阻塞当前线程。
一元依赖:任务的回调
thenRun/thenRunAsync
方法
CompletableFuture
的 thenRun
和 thenRunAsync
方法用于在异步操作完成后执行一个动作,这个动作由一个 Runnable
对象表示。这两个方法都用于在异步操作链中添加一个不返回值的操作,但它们在执行方式上有所不同。
- 区别:
thenRun
是在CompletableFuture
默认的执行器上执行Runnable
,而thenRunAsync
允许你指定一个自定义的Executor
来异步执行Runnable
。 - 返回值:两者都返回
CompletableFuture<Void>
类型,表示它们不返回任何有意义的值,即它们的操作不产生结果,通常用于执行副作用。 - 自定义线程池:
thenRun
使用默认的ForkJoinPool.commonPool()
,而thenRunAsync
可以使用自定义的线程池。 - 执行方式:
thenRun
是同步执行,而thenRunAsync
是异步执行。
thenRun
的具体使用方法
thenRun
方法在前一个 CompletableFuture
完成之后,会在默认的执行器上执行提供的 Runnable
。
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenRun(() -> System.out.println("World")); // 异步执行,打印 "World"
在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会在默认的执行器上执行 Runnable
,打印出 "World"。
thenRunAsync
的具体使用方法
thenRunAsync
方法在前一个 CompletableFuture
完成之后,会使用指定的 Executor
异步执行提供的 Runnable
。
ExecutorService executor = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenRunAsync(() -> System.out.println("World"), executor); // 使用自定义线程池执行
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executor.shutdown();
在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 thenRunAsync
来执行一个任务,该任务在指定的线程池中打印 "World"。
thenApply/thenApplyAsync
方法
CompletableFuture
的 thenApply
和 thenApplyAsync
方法用于在异步操作完成后,对结果应用一个函数,并将函数的返回值封装进一个新的 CompletableFuture
中。这两个方法允许对异步操作的结果进行转换或处理。
- 区别:
thenApply
使用CompletableFuture
的默认执行器来同步执行提供的函数,而thenApplyAsync
明确地异步执行函数,并且可以指定自定义的ExecutorService
。 - 返回值:两者都会返回一个新的
CompletableFuture
,其类型是应用函数后的结果类型。 - 自定义线程池:
thenApply
使用默认的ForkJoinPool.commonPool()
,而thenApplyAsync
可以使用自定义的ExecutorService
。 - 执行方式:
thenApply
是同步执行,thenApplyAsync
是异步执行。
thenApply
的具体使用方法
thenApply
方法在前一个 CompletableFuture
完成之后,会同步执行提供的函数。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World"); // 同步执行,将结果转换为 "Hello World"
System.out.println(future.join()); // 输出 "Hello World"
在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会同步执行 thenApply
中的函数,将结果转换为 "Hello World"。
thenApplyAsync
的具体使用方法
thenApplyAsync
方法在前一个 CompletableFuture
完成之后,会使用指定的 ExecutorService
异步执行提供的函数。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApplyAsync(s -> s + " World", executorService); // 使用自定义线程池异步执行
System.out.println(future.join()); // 输出 "Hello World"
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 thenApplyAsync
来异步地执行一个任务,该任务将结果转换为 "Hello World"。
thenCompose/thenComposeAsync
方法
CompletableFuture
的 thenCompose
和 thenComposeAsync
方法用于在异步操作完成后,将结果传递给另一个异步操作。这两个方法允许你将多个异步操作链接起来,形成链式调用。
- 区别:
thenCompose
是同步地将结果传递给另一个CompletableFuture
,而thenComposeAsync
是异步地进行传递,并且可以指定自定义的线程池。 - 返回值:两者都会返回一个新的
CompletableFuture
,其类型是传递给下一个异步操作的函数返回值的类型。 - 自定义线程池:
thenCompose
使用默认的执行器,而thenComposeAsync
允许你使用自定义的线程池。 - 执行方式:
thenCompose
是同步执行,thenComposeAsync
是异步执行。
thenCompose
的具体使用方法
thenCompose
方法接受一个函数,该函数返回另一个 CompletableFuture
。当当前 CompletableFuture
完成时,会立即使用它作为参数,调用提供的函数,并继续链式操作。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));
System.out.println(future.join());
在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会立即执行 thenCompose
中的函数,异步地供应 "Hello World"。
thenComposeAsync
的具体使用方法
thenComposeAsync
方法同样接受一个返回 CompletableFuture
的函数,但它明确地异步执行这个函数,并允许你指定一个自定义的 Executor
。
ExecutorService executor = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenComposeAsync(s -> CompletableFuture.supplyAsync(() -> s + " World"), executor);
System.out.println(future.join());
//在使用完自定义线程池后,应该关闭它以释放资源
executor.shutdown();
在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 thenComposeAsync
来异步地执行一个任务,该任务异步地供应 "Hello World"。
thenAccept/thenAcceptAsync
方法
CompletableFuture
的 thenAccept
和 thenAcceptAsync
方法用于在异步操作完成后对结果进行处理。它们都接受一个 Consumer
对象作为参数,该对象代表要对异步操作结果执行的操作。这两个方法的主要区别在于它们的执行方式和是否允许使用自定义线程池。
- 区别:
thenAccept
默认在CompletableFuture
的默认执行器上同步执行Consumer
,而thenAcceptAsync
明确地异步执行Consumer
,并允许指定自定义的ExecutorService
。 - 返回值:两者都返回
CompletableFuture<Void>
,表示它们不返回计算结果,即它们的操作不产生值。 - 自定义线程池:
thenAccept
使用默认的ForkJoinPool.commonPool()
,而thenAcceptAsync
可以使用自定义的ExecutorService
。 - 执行方式:
thenAccept
是同步执行,thenAcceptAsync
是异步执行。
thenAccept
的具体使用方法
thenAccept
方法在前一个 CompletableFuture
完成之后,会在默认的执行器上同步执行提供的 Consumer
。
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenAccept(s -> System.out.println(s + " World")); // 同步执行,打印 "Hello World"
在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会在默认的执行器上同步执行 Consumer
,打印出 "Hello World"。
thenAcceptAsync
的具体使用方法
thenAcceptAsync
方法在前一个 CompletableFuture
完成之后,会使用指定的 ExecutorService
异步执行提供的 Consumer
。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenAcceptAsync(s -> System.out.println(s + " World"), executorService); // 使用自定义线程池执行
System.out.println(future);
//在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 thenAcceptAsync
来执行一个任务,该任务在指定的线程池中打印 "Hello World"。
whenComplete/whenCompleteAsync
方法
CompletableFuture
的 whenComplete
和 whenCompleteAsync
方法用于在异步操作完成后执行一个操作,无论操作成功还是异常。这两个方法都接受一个 BiConsumer<? super T, ? super Throwable>
函数式接口的实现作为参数,该接口允许你访问异步操作的结果或者异常。这两个方法的主要区别在于它们的执行方式。
- 区别:
whenComplete
默认在CompletableFuture
的默认执行器上同步执行BiConsumer
,而whenCompleteAsync
明确地异步执行BiConsumer
,并允许指定自定义的ExecutorService
。 - 返回值:两者都返回
CompletableFuture<T>
,即它们返回的是原始CompletableFuture
的结果类型T
。 - 自定义线程池:
whenComplete
使用默认的ForkJoinPool.commonPool()
,而whenCompleteAsync
可以使用自定义的ExecutorService
。 - 执行方式:
whenComplete
是同步执行,whenCompleteAsync
是异步执行。
whenComplete
的具体使用方法
whenComplete
方法在 CompletableFuture
完成之后,无论是正常完成还是有异常,都会在默认的执行器上同步执行提供的 BiConsumer
。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.whenComplete((s, throwable) -> {
if (throwable != null) {
System.out.println("Error: " + throwable.getMessage());
} else {
System.out.println("Result: " + s);
}
});
// 可以链式调用 thenApply 来处理结果
CompletableFuture<String> finalFuture = future.thenApply(s -> s + " World");
System.out.println(finalFuture.join()); // 输出 "Hello World"
在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会在默认的执行器上同步执行 BiConsumer
,根据操作的结果或异常打印相应的信息。
whenCompleteAsync
的具体使用方法
whenCompleteAsync
方法在 CompletableFuture
完成之后,无论是正常完成还是有异常,都会使用指定的 ExecutorService
异步执行提供的 BiConsumer
。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.whenCompleteAsync((s, throwable) -> {
if (throwable != null) {
System.out.println("Error: " + throwable.getMessage());
} else {
System.out.println("Result: " + s);
}
}, executorService); // 使用自定义线程池执行
System.out.println(future);
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 whenCompleteAsync
来异步执行一个任务,该任务根据操作的结果或异常打印相应的信息。
handle/handleAsync
方法
CompletableFuture
的 handle
和 handleAsync
方法用于在异步操作完成后执行一个操作,并且这个操作可以处理异步操作的结果或者异常。这两个方法都接受一个 BiFunction<? super T, Throwable, ? extends U>
函数式接口的实现作为参数,该接口允许你访问异步操作的结果或者捕获的异常,并返回一个新的值。
- 区别:
handle
在CompletableFuture
的默认执行器上同步执行BiFunction
,而handleAsync
明确地异步执行BiFunction
,并允许指定自定义的ExecutorService
。 - 返回值:两者都返回
CompletableFuture<U>
,即它们返回一个新的CompletableFuture
实例,其类型U
是由handle
或handleAsync
方法中的函数返回的。 - 自定义线程池:
handle
使用默认的ForkJoinPool.commonPool()
,而handleAsync
可以使用自定义的ExecutorService
。 - 执行方式:
handle
是同步执行,handleAsync
是异步执行。
handle
的具体使用方法
handle
方法在 CompletableFuture
完成之后,无论是正常完成还是有异常,都会在默认的执行器上同步执行提供的 BiFunction
。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.handle((s, throwable) -> {
if (throwable != null) {
System.out.println("Error occurred: " + throwable.getMessage());
return "Error";
} else {
System.out.println("Result: " + s);
return s + " World";
}
});
// 可以链式调用其他方法来继续处理返回的值
CompletableFuture<String> finalFuture = future.thenApply(result -> "Final: " + result);
// 根据前面的handle方法,可能输出 "Final: Hello World"
System.out.println(finalFuture.join());
handleAsync
的具体使用方法
handleAsync
方法在 CompletableFuture
完成之后,无论是正常完成还是有异常,都会使用指定的 ExecutorService
异步执行提供的 BiFunction
。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.handleAsync((s, throwable) -> {
if (throwable != null) {
System.out.println("Error occurred: " + throwable.getMessage());
return "Error";
} else {
System.out.println("Result: " + s);
return s + " World";
}
}, executorService); // 使用自定义线程池执行
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
// 可以链式调用其他方法来继续处理返回的值
CompletableFuture<String> finalFuture = future.thenApply(result -> "Final: " + result);
// 根据前面的handleAsync方法,可能输出 "Final: Hello World"
System.out.println(finalFuture.join());
一元依赖使用方式总结
这些方法的主要区别在于它们对异步性的处理(同步或异步)以及是否允许使用自定义的线程池。使用这些方法时,可以根据具体需求选择最合适的操作,以实现对异步计算结果的灵活处理。
二元依赖:AND组合关系
thenCombine/thenCombineAsync
方法
CompletableFuture
的 thenCombine
和 thenCombineAsync
方法用于将两个异步操作的结果组合起来。当两个 CompletableFuture
都完成时,可以利用这两个结果执行某些操作。
- 区别:
thenCombine
在任一输入的CompletableFuture
完成时,使用默认的执行器来同步执行提供的组合函数。thenCombineAsync
允许异步执行组合函数,并且可以指定自定义的ExecutorService
。 - 返回值:两者都返回
CompletableFuture<V>
,其中V
是组合函数返回的值的类型。 - 自定义线程池:
thenCombine
使用默认的ForkJoinPool.commonPool()
,而thenCombineAsync
可以使用自定义的ExecutorService
。 - 执行方式:
thenCombine
是同步执行,而thenCombineAsync
是异步执行。
thenCombine
的具体使用方法
thenCombine
方法在两个 CompletableFuture
都完成时,使用它们的结果同步执行一个组合函数。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> s1 + ", " + s2);
System.out.println(combinedFuture.join()); // 输出 "Hello, World"
在这个例子中,我们异步地创建了两个 CompletableFuture
实例,当它们都完成时,使用它们的结果通过 thenCombine
方法组合成一个新的字符串。
thenCombineAsync
的具体使用方法
thenCombineAsync
方法在两个 CompletableFuture
都完成时,使用指定的 ExecutorService
异步执行组合函数。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combinedAsyncFuture = future1.thenCombineAsync(future2, (s1, s2) -> s1 + ", " + s2, executorService);
System.out.println(combinedAsyncFuture.join()); // 输出 "Hello, World"
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
在这个例子中,我们使用 thenCombineAsync
方法并指定了一个自定义线程池来异步组合两个异步操作的结果。
thenAcceptBoth/thenAcceptBothAsync
方法
CompletableFuture
的 thenAcceptBoth
和 thenAcceptBothAsync
方法用于在两个异步操作完成后,使用两个 CompletableFuture
的结果执行一个动作。这两个方法都接受一个 BiAction
作为参数,该 BiAction
接受两个参数:第一个 CompletableFuture
的结果和第二个 CompletableFuture
的结果。
- 区别:
thenAcceptBoth
在任一输入的CompletableFuture
完成时,使用默认的执行器来同步执行提供的BiAction
。thenAcceptBothAsync
允许异步执行BiAction
,并且可以指定自定义的ExecutorService
。 - 返回值:两者都返回
CompletableFuture<Void>
,因为BiAction
不返回任何结果。 - 自定义线程池:
thenAcceptBoth
使用默认的ForkJoinPool.commonPool()
,而thenAcceptBothAsync
可以使用自定义的ExecutorService
。 - 执行方式:
thenAcceptBoth
是同步执行,而thenAcceptBothAsync
是异步执行。
thenAcceptBoth
的具体使用方法
thenAcceptBoth
方法在两个 CompletableFuture
都完成时,使用它们的结果同步执行一个 BiAction
。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> bothFuture = future1.thenAcceptBoth(future2, (s1, s2) -> System.out.println(s1 + " " + s2));
// 输出 "Hello World",join() 用于等待操作完成
bothFuture.join();
thenAcceptBothAsync
的具体使用方法
thenAcceptBothAsync
方法在两个 CompletableFuture
都完成时,使用指定的 ExecutorService
异步执行 BiAction
。
// 创建自定义的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> bothAsyncFuture = future1.thenAcceptBothAsync(future2, (s1, s2) -> System.out.println(s1 + " " + s2), executorService);
// 输出 "Hello World",join() 用于等待操作完成
bothAsyncFuture.join();
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
runAfterEither/runAfterEitherAsync
方法
CompletableFuture
的 runAfterBoth
和 runAfterBothAsync
方法用于在两个异步操作完成后执行一个 Runnable
。这两个方法允许你指定一个动作,这个动作会在两个 CompletableFuture
都完成之后执行,与它们完成的顺序无关。
- 区别:
runAfterBoth
在任一输入的CompletableFuture
完成时,使用默认的执行器来同步执行提供的Runnable
。runAfterBothAsync
允许异步执行Runnable
,并且可以指定自定义的ExecutorService
。 - 返回值:两者都返回
CompletableFuture<Void>
,因为Runnable
不返回任何结果。 - 自定义线程池:
runAfterBoth
使用默认的ForkJoinPool.commonPool()
,而runAfterBothAsync
可以使用自定义的ExecutorService
。 - 执行方式:
runAfterBoth
是同步执行,而runAfterBothAsync
是异步执行。
runAfterBoth
的具体使用方法
runAfterBoth
方法在两个 CompletableFuture
都完成时,使用它们的结果同步执行一个 Runnable
。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> bothFuture = future1.runAfterBoth(future2, () -> System.out.println("Both futures are complete."));
// 输出 "Both futures are complete.",join() 用于等待操作完成
bothFuture.join();
runAfterBothAsync
的具体使用方法
runAfterBothAsync
方法在两个 CompletableFuture
都完成时,使用指定的 ExecutorService
异步执行 Runnable
。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> bothAsyncFuture = future1.runAfterBothAsync(future2, () -> System.out.println("Both futures are complete."), executorService);
// 输出 "Both futures are complete.",join() 用于等待操作完成
bothAsyncFuture.join();
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
二元依赖:OR组合的关系
applyToEither/applyToEitherAsync
方法
CompletableFuture
的 applyToEither
和 applyToEitherAsync
方法用于在两个异步操作中,无论哪个操作先完成,都对第一个完成的操作的结果应用一个函数,并将函数的返回值封装进一个新的 CompletableFuture
中。
- 区别:
applyToEither
会在任一提供的CompletableFuture
完成时,使用默认的执行器来同步执行提供的函数。applyToEitherAsync
同样会在任一提供的CompletableFuture
完成时执行函数,但它明确地异步执行函数,并且可以指定自定义的ExecutorService
。 - 返回值:两者都会返回一个新的
CompletableFuture
,其类型是应用函数后的结果类型。 - 自定义线程池:
applyToEither
使用默认的ForkJoinPool.commonPool()
,而applyToEitherAsync
可以使用自定义的ExecutorService
。 - 执行方式:
applyToEither
是同步执行,applyToEitherAsync
是异步执行。
applyToEither
的具体使用方法
applyToEither
方法在两个 CompletableFuture
中任何一个完成时,会同步执行提供的函数。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> eitherFuture = future1.applyToEither(future2, s -> s.toUpperCase());
System.out.println(eitherFuture.join()); // 输出第一个完成的 CompletableFuture 的结果转换为大写
在这个例子中,我们异步地供应字符串 "Hello" 和 "World",无论哪个 CompletableFuture
先完成,都会同步执行 applyToEither
中的函数,将其结果转换为大写。
applyToEitherAsync
的具体使用方法
applyToEitherAsync
方法在两个 CompletableFuture
中任何一个完成时,会使用指定的 ExecutorService
异步执行提供的函数。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> eitherAsyncFuture = future1.applyToEitherAsync(future2, s -> s.toUpperCase(), executorService);
System.out.println(eitherAsyncFuture.join()); // 输出第一个完成的 CompletableFuture 的结果转换为大写
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 和 "World" 之后,使用 applyToEitherAsync
来异步地执行一个任务,该任务将第一个完成的 CompletableFuture
的结果转换为大写。
acceptEither/acceptEitherAsync
方法
CompletableFuture
的 acceptEither
和 acceptEitherAsync
方法用于在两个异步操作中,无论哪个操作先完成,都执行一个 Consumer
对象,该 Consumer
接受操作的结果。
- 区别:
acceptEither
会在任一提供的CompletableFuture
完成时,使用默认的执行器来同步执行提供的Consumer
。acceptEitherAsync
也会在任一提供的CompletableFuture
完成时执行Consumer
,但它明确地异步执行Consumer
,并且可以指定自定义的ExecutorService
。 - 返回值:两者都返回
CompletableFuture<Void>
,因为Consumer
接受一个参数并不返回任何结果。 - 自定义线程池:
acceptEither
使用默认的ForkJoinPool.commonPool()
,而acceptEitherAsync
可以使用自定义的ExecutorService
。 - 执行方式:
acceptEither
是同步执行,acceptEitherAsync
是异步执行。
acceptEither
的具体使用方法
acceptEither
方法在两个 CompletableFuture
中任何一个完成时,会同步执行提供的 Consumer
。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> eitherFuture = future1.acceptEither(future2, s -> System.out.println(s));
eitherFuture.join(); // 输出第一个完成的 CompletableFuture 的结果
在这个例子中,我们异步地供应字符串 "Hello" 和 "World",无论哪个 CompletableFuture
先完成,都会同步执行 acceptEither
中的 Consumer
,打印出结果。
acceptEitherAsync
的具体使用方法
acceptEitherAsync
方法在两个 CompletableFuture
中任何一个完成时,会使用指定的 ExecutorService
异步执行提供的 Consumer
。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> eitherAsyncFuture = future1.acceptEitherAsync(future2, s -> System.out.println(s), executorService);
eitherAsyncFuture.join(); // 输出第一个完成的 CompletableFuture 的结果
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 和 "World" 之后,使用 acceptEitherAsync
来异步地执行一个任务,该任务打印出第一个完成的 CompletableFuture
的结果。
runAfterEither/runAfterEitherAsync
方法
CompletableFuture
的 runAfterEither
和 runAfterEitherAsync
方法用于在两个异步操作中的任何一个完成后执行一个 Runnable
。这两个方法允许你指定一个动作,这个动作会在任何一个 CompletableFuture
完成之后执行,与它们完成的顺序无关。
- 区别:
runAfterEither
会在任一提供的CompletableFuture
完成时,使用默认的执行器来同步执行提供的Runnable
。runAfterEitherAsync
也会在任一提供的CompletableFuture
完成时执行Runnable
,但它明确地异步执行Runnable
,并且可以指定自定义的ExecutorService
。 - 返回值:两者都返回
CompletableFuture<Void>
,因为Runnable
接受没有参数并不返回任何结果。 - 自定义线程池:
runAfterEither
使用默认的ForkJoinPool.commonPool()
,而runAfterEitherAsync
可以使用自定义的ExecutorService
。 - 执行方式:
runAfterEither
是同步执行,runAfterEitherAsync
是异步执行。
runAfterEither
的具体使用方法
runAfterEither
方法在两个 CompletableFuture
中任何一个完成时,会同步执行提供的 Runnable
。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> eitherFuture = future1.runAfterEither(future2, () -> System.out.println("One of the futures is complete."));
eitherFuture.join(); // 确保 Runnable 执行完毕
在这个例子中,我们异步地供应字符串 "Hello" 和 "World",无论哪个 CompletableFuture
先完成,都会同步执行 runAfterEither
中的 Runnable
,打印出一条消息。
runAfterEitherAsync
的具体使用方法
runAfterEitherAsync
方法在两个 CompletableFuture
中任何一个完成时,会使用指定的 ExecutorService
异步执行提供的 Runnable
。
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> eitherAsyncFuture = future1.runAfterEitherAsync(future2, () -> System.out.println("One of the futures is complete."), executorService);
eitherAsyncFuture.join(); // 确保 Runnable 执行完毕
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 和 "World" 之后,使用 runAfterEitherAsync
来异步地执行一个任务,该任务打印出一条消息。
二元依赖使用方式总结
CompletableFuture
的 AND 组合关系方法(thenCombine
, thenCombineAsync
, thenAcceptBoth
, thenAcceptBothAsync
, runAfterBoth
, runAfterBothAsync
)在两个 CompletableFuture
都成功完成后执行某个操作,而 OR 组合关系方法(applyToEither
, applyToEitherAsync
, acceptEither
, acceptEitherAsync
, runAfterEither
, runAfterEitherAsync
)在两个 CompletableFuture
中任意一个完成时执行操作,其中 AND 组合关系方法关注结果的组合和消费,OR 组合关系方法关注对第一个完成的结果的快速反应。
多元依赖:allOf/anyOf
方法
CompletableFuture
的 allOf
和 anyOf
方法用于处理多个异步操作的完成情况。
allOf
:等待多个CompletableFuture
都完成。anyOf
:等待多个CompletableFuture
中的任何一个完成。- 区别:
allOf
要求所有给定的CompletableFuture
对象都完成,而anyOf
只要求至少有一个完成。 - 返回值:两者都返回一个新的
CompletableFuture<Void>
,不包含具体业务返回值,表示所有或任何一个异步操作的完成。 - 自定义线程池:这两个方法本身不涉及执行具体的异步操作,因此它们不使用线程池。它们仅在所有给定的
CompletableFuture
都完成时触发,而实际的异步操作可以使用自定义线程池。 - 执行方式:
allOf
和anyOf
本身是同步执行的,因为它们仅在所有或任一CompletableFuture
异步操作完成后才会触发。
allOf
的具体使用方法
allOf
方法在所有提供的 CompletableFuture
完成时,会完成返回的 CompletableFuture
。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "!");
CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2, future3);
allFuture.thenRun(() -> System.out.println("Both futures are complete."));
如果想要获取每个 CompletableFuture
的结果,你可以使用 join()
方法或者 get()
方法在每个独立的 CompletableFuture
上。以下是如何获取这些返回值的示例:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "!");
CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2, future3);
// 等待所有异步操作都完成
allFuture.join();
// 现在所有 future 都完成了,可以安全地获取它们的返回值
String result1 = future1.join(); // 获取 "Hello"
String result2 = future2.join(); // 获取 "World"
String result3 = future3.join(); // 获取 "!"
// 可以按需对结果进行处理,例如拼接字符串
String combinedResult = result1 + " " + result2 + " " + result3;
System.out.println(combinedResult); // 输出 "Hello World !"
在这个例子中,我们首先启动了三个异步操作,并通过 allOf
方法创建了一个等待所有这些操作都完成的 CompletableFuture
。一旦 allFuture.join()
返回,我们就可以安全地调用 join()
方法在 future1
、future2
和 future3
上,以获取它们的返回值。
请注意,join()
方法是阻塞的,它会等待对应的 CompletableFuture
完成。如果你在调用 join()
的时候不希望阻塞当前线程,可以使用 get()
方法,它提供了超时机制:
String result1 = future1.get(); // 这将阻塞直到 future1 完成
String result2 = future2.get(); // 这将阻塞直到 future2 完成
String result3 = future3.get(); // 这将阻塞直到 future3 完成
如果你确实需要异步地获取这些结果,并且对完成顺序没有特定要求,你可以使用 thenApply
、thenAccept
或者 whenComplete
等方法来处理每个 CompletableFuture
的结果。
anyOf
的具体使用方法
anyOf
方法在任一提供的 CompletableFuture
完成时,就会完成返回的 CompletableFuture
。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "!");
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2, future3);
anyFuture.thenAccept(completedFuture ->
System.out.println("One of the futures is complete."));
如果想要获取每个 CompletableFuture
的返回值,你需要分别对它们调用 join()
或 get()
方法。以下是如何获取这些返回值的示例:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "!");
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2, future3);
anyFuture.thenAccept(completedFuture ->
System.out.println("One of the futures is complete."));
// 等待 anyFuture 完成,但这只告诉我们至少有一个 future 完成了,并不指明是哪一个
anyFuture.join();
// 检查每个 future 是否完成,并获取其结果
String result1 = future1.isDone() ? future1.join() : null;
String result2 = future2.isDone() ? future2.join() : null;
String result3 = future3.isDone() ? future3.join() : null;
// 打印结果
System.out.println("Result from future1: " + result1);
System.out.println("Result from future2: " + result2);
System.out.println("Result from future3: " + result3);
在这个例子中,我们首先启动了三个异步操作,并通过 anyOf
方法创建了一个 CompletableFuture
,该 CompletableFuture
将在任一异步操作完成时完成。我们使用 thenAccept
方法来处理至少一个异步操作完成的情况。
然后,我们使用 join()
方法等待 anyFuture
完成,随后检查每个 CompletableFuture
是否完成,并通过调用 join()
来获取它们的返回值。这里我们假设一旦 anyFuture
完成,其他 CompletableFuture
也可能会完成。如果某个 CompletableFuture
还没有完成,join()
方法会阻塞当前线程直到它完成。
请注意,这种方法可能不是效率最高的,特别是如果 CompletableFuture
完成的时间差异较大时。如果你确实需要知道哪个 CompletableFuture
首先完成,你可能需要使用其他策略,例如为每个 CompletableFuture
添加一个单独的完成处理程序,或者使用原子引用来存储第一个完成的结果。
注意事项
allOf
和anyOf
本身不执行异步操作,它们只是等待其他CompletableFuture
的完成。因此,使用这些方法时,你的异步操作应该已经通过其他方式(如supplyAsync
)在自定义线程池中执行。- 这些方法通常用于在异步操作完成后执行某些同步逻辑,如结果汇总、后续步骤触发等。
- 使用
allOf
时,如果任何一个CompletableFuture
完成异常,返回的CompletableFuture
也会异常结束。而anyOf
会忽略其他CompletableFuture
的异常,只关注任一完成。
异常处理: exceptionally
CompletableFuture
的 exceptionally
方法用于处理异步操作中的异常。当 CompletableFuture
完成时,如果它完成了一个异常(即操作失败),那么 exceptionally
方法中提供的函数将被调用。这个方法允许你提供一个函数,该函数能够返回一个非空的值,这个值将用作 CompletableFuture
的结果。
- 返回值:
exceptionally
方法返回CompletableFuture<T>
,其中T
是函数返回的类型。 - 自定义线程池:
exceptionally
方法本身不涉及线程池的使用,它总是在CompletableFuture
所在线程的默认执行器上同步执行。 - 执行方式:
exceptionally
是同步执行的。
具体使用方法
exceptionally
方法在 CompletableFuture
由于异常而完成时,执行一个函数来处理这个异常,并返回一个替代的结果。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("Operation failed!");
}
return 42;
}).exceptionally(throwable -> {
// 异常处理逻辑
System.out.println("Handling exception: " + throwable.getMessage());
// 返回一个替代的结果
return -1;
});
System.out.println("Future result: " + future.join()); // 如果有异常,输出将是 -1
在这个例子中,我们异步地执行一个可能抛出异常的操作。如果操作失败并抛出异常,exceptionally
方法中的函数将被调用,打印异常信息,并返回一个替代的结果 -1
。
注意事项
exceptionally
方法只能捕获并处理异步操作中的异常,它不能捕获在调用exceptionally
方法之后发生的异常。exceptionally
方法应该谨慎使用,因为它可能会隐藏错误并导致程序状态不一致。通常,更推荐使用handle
方法来同时处理正常结果和异常。- 由于
exceptionally
方法在CompletableFuture
完成的线程上执行,所以它不会创建新的线程,也不涉及自定义线程池的使用。
获取任务状态:isCancelled/isCompletedExceptionally
CompletableFuture
类中的 isCancelled
和 isCompletedExceptionally
是两个用于检查异步操作状态的方法。下面是它们的使用方式和区别:
isCancelled()
的具体使用方法
isCancelled()
方法用于检查 CompletableFuture
是否已经被取消。如果 CompletableFuture
被取消,且在取消时它还没有完成任何其他终端状态(即没有成功执行也没有异常),则返回 true
。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 执行一些异步操作
System.out.println("Async operation completed");
});
// 尝试取消 future,参数 true 表示中断正在执行的线程
future.cancel(true);
// 检查是否取消
boolean isCancelled = future.isCancelled();
System.out.println("Is future cancelled? " + isCancelled);
isCompletedExceptionally()
的具体使用方法
isCompletedExceptionally()
方法用于检查 CompletableFuture
是否由于异常而完成。如果 CompletableFuture
由于异常(包括取消异常)而完成,返回 true
。
直接调用 future.isCompletedExceptionally()
不会立即返回 true
,因为 CompletableFuture.runAsync()
中的代码是异步执行的,所以异常抛出的操作还没有完成。要确保能够检查到异常完成状态,你需要等待 CompletableFuture
真正完成。这可以通过调用 join()
或 get()
方法实现,但这两种方法都是阻塞的。
可以通过几种方式实现:
- 在提供的异步函数中抛出异常。
- 使用
completeExceptionally
方法手动完成CompletableFuture
并传入一个异常。
示例 1: 异步函数抛出异常
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
throw new RuntimeException("Async operation failed");
});
try {
// 等待 future 完成
future.join();
} catch (CompletionException e) {
// 如果 future 由于异常而完成,CompletionException 会被抛出
System.out.println("Async operation failed: " + e.getCause().getMessage());
}
boolean completedExceptionally = future.isCompletedExceptionally();
// 如果抛出了异常,这里将输出 true
System.out.println("Is future completed exceptionally? " + completedExceptionally);
在这个示例中,异步函数抛出了一个 RuntimeException
,这将导致 CompletableFuture
异常完成,因此 isCompletedExceptionally()
将返回 true
。
示例 2: 使用 completeExceptionally
手动完成
CompletableFuture<Void> future = new CompletableFuture<>();
// 模拟异步操作中出现的异常情况
future.completeExceptionally(new RuntimeException("Manual exception completion"));
boolean completedExceptionally = future.isCompletedExceptionally();
// 将输出 true
System.out.println("Is future completed exceptionally? " + completedExceptionally);
在这个示例中,我们使用 completeExceptionally
方法手动将 CompletableFuture
设置为异常完成状态,并传入一个 RuntimeException
对象。这样,isCompletedExceptionally()
也会返回 true
。
请注意,一旦 CompletableFuture
通过 completeExceptionally
被设置为异常完成,它就不能被取消或正常完成了。此外,使用 completeExceptionally
是在 CompletableFuture
尚未完成时的一种操作,如果它已经完成了,那么调用 completeExceptionally
将没有效果。
使用场景
isCancelled()
通常用于确定是否需要采取一些操作,比如资源清理,当知道CompletableFuture
被取消了之后。isCompletedExceptionally()
可以在你想要检查CompletableFuture
是否由于遇到问题而未能正常完成时使用,这包括了操作失败的异常以及完成时的取消异常。
注意事项
- 一旦
CompletableFuture
完成,无论是正常完成、异常完成还是被取消,isCancelled
和isCompletedExceptionally
的状态就不会改变。 - 在调用
isCancelled()
或isCompletedExceptionally()
之前,你应该确保CompletableFuture
已经达到了一个终端状态,否则这两个方法可能返回不准确的结果。
这两个方法是非阻塞的,通常用于异步操作的协调和状态检查。
状态监控:getNumberOfDependents
CompletableFuture
的 getNumberOfDependents
方法返回一个整数,表示有多少异步操作或者依赖是注册在当前 CompletableFuture
实例上的。这个方法可以用来了解和调试 CompletableFuture
对象上的链式调用情况。
这个方法的使用非常简单,它不涉及任何参数,直接返回一个整数值:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello";
});
// 添加一些依赖操作
future.thenApply(s -> s.toUpperCase());
future.thenAccept(s -> System.out.println(s));
future.exceptionally(e -> {
System.out.println("An exception occurred: " + e.getMessage());
return null;
});
// 获取依赖的数量
int numberOfDependents = future.getNumberOfDependents();
System.out.println("Number of dependents: " + numberOfDependents);
如果在调用 getNumberOfDependents
方法时,所有的依赖操作都还没有完成,那么理论上 numberOfDependents
应该返回 3
,因为您添加了三个依赖。
然而,有几个原因可能导致 getNumberOfDependents
返回 0
:
- 异步操作完成太快:如果
supplyAsync
非常快速地完成了,那么在您调用getNumberOfDependents
之前,所有的依赖操作可能都已经完成了。 - 调用顺序问题:如果在调用
getNumberOfDependents
之前,您做了某些事情(如调用了join
、get
或其他可能导致CompletableFuture
完成的操作),那么这可能触发了依赖的执行和完成。 JDK
实现细节:getNumberOfDependents
方法的实现可能依赖于内部的计数器,这些计数器可能在依赖完成时递减。如果依赖完成得非常快,或者在调用getNumberOfDependents
之前有其他操作影响了这些计数器,那么它们可能已经被清零了。
为了确保 getNumberOfDependents
返回正确的数量,您可以尝试以下方法:
- 确保在调用
getNumberOfDependents
之前,没有执行任何可能导致CompletableFuture
或其依赖完成的操作。 - 使用
CompletableFuture
的whenComplete
方法来在异步操作完成后立即调用getNumberOfDependents
。
请注意,getNumberOfDependents
主要用于调试目的,不应该依赖它来控制程序的主要逻辑。在实际应用中,依赖的数量通常不是非常重要,更重要的是正确地管理和协调异步操作。
强制处理:obtrudeValue/obtrudeException
方法
obtrudeValue(T value)
的具体使用方法
obtrudeValue
方法用于将 CompletableFuture
的结果强制设置为提供的值。如果 CompletableFuture
已经以任何方式完成(无论是正常完成、异常完成还是被取消),那么调用 obtrudeValue
将不会有任何效果。
CompletableFuture<String> future = new CompletableFuture<>();
future.obtrudeValue("Hello World"); // 强制完成 future 并赋予值
future.thenAccept(System.out::println); // 这将打印 "Hello World"
obtrudeException(Throwable throwable)
的具体使用方法
obtrudeException
方法用于将 CompletableFuture
的结果强制设置为由提供的异常引起的异常完成。同样,如果 CompletableFuture
已经完成,调用 obtrudeException
将不会有任何效果。
CompletableFuture<String> future = new CompletableFuture<>();
future.obtrudeException(new RuntimeException("Something went wrong")); // 强制完成 future 并赋予异常
future.exceptionally(e -> {
System.out.println(e.getMessage()); // 这将打印异常信息 "Something went wrong"
return null;
});
使用场景
obtrudeValue
和obtrudeException
可以在需要覆盖CompletableFuture
的完成状态时使用,例如,在某些错误恢复场景中,你可能需要将CompletableFuture
强制设置为成功或失败状态。- 这些方法也可以用来在程序的其他部分手动触发
CompletableFuture
的完成,即使异步操作尚未开始或已经取消。
注意事项
obtrudeValue
和obtrudeException
应该谨慎使用,因为它们会覆盖CompletableFuture
的自然完成状态,可能导致程序逻辑的混乱。- 一旦
CompletableFuture
完成,无论是通过正常完成、异常完成、取消还是通过这些方法之一,它的状态就不能被改变了。 - 这些方法适用于需要手动干预异步操作结果的场景,例如,在某些错误恢复策略中,或者当你需要根据外部事件强制完成
CompletableFuture
时。
在实际编程中,通常推荐使用 CompletableFuture
的标准完成方法,如 complete
和 completeExceptionally
,因为它们更符合 CompletableFuture
的预期使用模式。obtrudeValue
和 obtrudeException
应该用于特定的、非标准的场景。