1. 首页
  2. java并发

008-八、Java并发 Java CompletableFuture ( 上 )

引言

本文我们来了解下 Java 8 引入的 CompletableFuture 类,了解下该类提供的功能和用例。

Java 中的异步计算

异步计算很难推理的,因为我们的大脑是同步的,会将任何计算看成是一系列的同步计算。

我们在实现异步计算时,往往会把回调的动作分散在代码中或者深深地嵌套在彼此内部,这种情况下,当我们需要处理其中一个步骤中可能发生的错误时,情况变得更糟。

人生的一大悲剧是,尽管 Java 5 已经看到了这种恶性循环,提供了Future 接口作为异步计算的结果,但它没有提供任何方法来组合这些计算或处理可能的错误。

直到 Java 8,才引入了 CompletableFuture 类。该类不仅实现了 Future 接口,还实现了 CompletionStage 接口。此接口定义了可与异步计算步骤组合的异步计算步骤契约。

官方文档真是拗口,简单来说,CompletionStage 接口规范了一个异步计算步骤如何与另一个异步计算步骤组合。

CompletableFuture 类还是一个集大成者,即是一个构建块,也是一个框架,提供了大约 50 种不同的方法来构造,组合,执行异步计算步骤和处理错误。

API 数量如此之多,第一眼看到简直就傻眼了,不过好在它们可以分门别类,因为它们大多属于几个明确且不同的用例。

将 CompletableFuture 当作简单的 Future 使用

为什么可以 ?

因为 CompletableFuture 类实现了 Future 接口,因此我们可以将其用作 Future 实现,但需要自己实现额外的完成逻辑。

例如,我们可以使用无任何参数的构造函数来创建此类的实例,用于表示未来的某些结果,然后将其交给使用者,并在将来的某个时间调用 complete() 方法完成。消费者可以使用 get() 方法来阻止当前线程,直到提供此结果。


public Future<String> calculateAsync() throws InterruptedException { CompletableFuture<String> completableFuture = new CompletableFuture<>(); Executors.newCachedThreadPool().submit(() -> { Thread.sleep(500); completableFuture.complete("Hello"); return null; }); return completableFuture; }

上面的实例中,我们创建了一个创建 CompletableFuture 实例的方法,把计算分离到另一个线程中并立即返回 Future。当计算完成后,该方法通过将结果提供给 complete() 方法来完成 Future

为了分离计算,我们使用了前几章节 一文秒懂 Java 线程池 ( Thread Pool ) (上) 中提到的 Executor API。这种创建和完成 CompletableFuture 的方法可以与任何并发机制或 API( 包括原始线程 )一起使用。

请注意,calculateAsync() 方法返回 Future 实例。

接下来,我们只要调用此方法,接收 Future 实例并在我们准备阻塞结果时调用它的 get()方法。


Future<String> completableFuture = calculateAsync(); // ... String result = completableFuture.get(); assertEquals("Hello", result);

注意: get() 方法会抛出一些已检查的异常,即 ExecutionException( 封装计算期间发生的异常 )和 InterruptedException( 表示执行方法的线程被中断的异常 )。

如果你已经知道计算的结果,可以将表示此计算的结果作为参数传递给 completedFuture() 静态方法,这样,Futureget() 方法永远不会阻塞,而是立即返回此结果。


Future<String> completableFuture = CompletableFuture.completedFuture("Hello"); // ... String result = completableFuture.get(); assertEquals("Hello", result);

当然了,有时候,你可能希望取消 Future 的执行。

假设我们没有找到结果并决定完全取消异步执行,这可以通过调用 Futurecancel() 方法完成。此方法接收一个布尔参数 mayInterruptIfRunning


public Future<String> calculateAsyncWithCancellation() throws InterruptedException { CompletableFuture<String> completableFuture = new CompletableFuture<>(); Executors.newCachedThreadPool().submit(() -> { Thread.sleep(500); completableFuture.cancel(false); return null; }); return completableFuture; }

在上面这个异步方法的修改版本的范例中,当我们使用 Future.get() 方法阻塞结果时,如果 future 取消了,那么将抛出 CancellationException 异常。

但是,在类型为 CompletableFuture 的情况下,cancel() 方法没有任何效果,因为 CompletableFuture 并不会响应中断也不会处理中断。

用于封装计算逻辑的 CompletableFuture

上面讲解的这些代码,都允许我们选择任何并发执行机制,但是,如果我们想跳过这个样板代码并简单地异步执行一些代码呢?

CompletableFuture 的静态方法 runAsync()supplyAsync() 允许我们从 RunnableSupplier 中创建 CompletableFuture 实例。 RunnableSupplier 都是功能接口,由 Java 8 的新功能,可以将它们的实例作为lambda 表达式传递:

  • Runnable 接口与线程中使用的旧接口相同,不允许返回值
  • Supplier 接口是一个通用的功能接口,只有一个没有参数的方法,并返回一个参数化类型的值

下面的代码演示了如何将 Supplier 的实例作为 lambda 表达式参数,该表达式执行计算并返回结果


CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello"); // ... assertEquals("Hello", future.get());

处理异步计算的结果

处理计算结果的最通用方法是将其提供给函数。CompletableFuture.thenApply() 方法就是这样做的: 接受一个 Function 实例,用它来处理结果并返回一个用于保存函数返回的值 Future


CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<String> future = completableFuture .thenApply(s -> s + " World"); assertEquals("Hello World", future.get());

如果你不需要在 Future 链中返回值,则可以使用 Consumer 功能接口的实例。它只有一个方法,该方法接受一个参数并返回 void。而相应的,CompletableFuture 也提供了一个使用 Consumer 实例的方法 thenAccept() 。该方法接收一个 Consumer 并将其传递给计算结果。


CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<Void> future = completableFuture .thenAccept(s -> System.out.println("Computation returned: " + s)); future.get();

上面这个示例中,最后的 future.get() 方法会返回空值 Void

最后,如果你既不需要计算的值也不想在链的末尾返回一些值,那么你可以将 Runnable lambda 传递给 thenRun() 方法。


CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<Void> future = completableFuture .thenRun(() -> System.out.println("计算完成")); future.get();

上面这个示例中,调用 future.get() 方法之后会在控制台打印一行 计算完成

写完了如果写得有什么问题,希望读者能够给小编留言,也可以点击[此处扫下面二维码关注微信公众号](https://www.ycbbs.vip/?p=28 "此处扫下面二维码关注微信公众号")

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
  4. JS中文网,Javascriptc中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,是给开发者用的 Hacker News,技术文章由为你筛选出最优质的干货,其中包括:Android、iOS、前端、后端等方面的内容。目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。

    本文著作权归作者所有,如若转载,请注明出处

    转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com

    标题:008-八、Java并发 Java CompletableFuture ( 上 )

    链接:https://www.javajike.com/article/1275.html

« 009-九、Java并发 Java CompletableFuture ( 下 )
007-七、Java并发 Java Google Guava 实现»

相关推荐

QR code