CompletableFuture入门

1、Future 

1.1 Future简介

Future 类是异步思想的典型运用,主要用在一些需要执行耗时任务的场景,避免程序一直原地等待耗时任务执行完成,执行效率太低。举个例子来说:当我们执行某一耗时的任务时,可以将这个耗时任务交给一个子线程去异步执行,同时我们可以干点其他事情,不用等待耗时任务执行完成。等我们的事情干完后,我们再通过 Future 类获取到耗时任务的执行结果。这样一来,程序的执行效率就明显提高了。在 Java 中,Future 类只是一个泛型接口,位于 java.util.concurrent 包下,其中定义了 5 个方法,如下图所示:

 2、CompletableFuture 

2.1 CompletableFuture简介

Future 在实际使用过程中存在一些局限性比如不支持异步任务的组合、没有异常处理。Java 8 引入的CompletableFuture 类可以解决Future的这些缺陷。CompletableFuture实现了Future接口和CompletionStage接口,所以CompletableFuture 除了继承了Future 特性之外,还提供了函数式编程、异步任务编排组合(链式调用)等能力。 

2.2 CompletionStage接口

这个接口描述了一个异步计算的阶段。很多计算可以分成多个阶段或步骤,此时可以通过它将所有步骤组合起来,形成异步计算的流水线。CompletionStage接口中的方法比较多,CompletableFuture的函数式能力就是这个接口赋予的。从这个接口的方法参数你就可以发现其大量使用了 Java8 引入的函数式编程。

 2.3 CompletableFuture 的基本用法

2.3.1 创建 CompletableFuture 对象

使用CompletableFuture创建异步任务非常简单。可以使用CompletableFuture.supplyAsync()
CompletableFuture.runAsync() 方法来创建CompletableFuture对象。

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
// 使用自定义线程池(推荐)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
public static CompletableFuture<Void> runAsync(Runnable runnable);
// 使用自定义线程池(推荐)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);

runAsync() 方法接受的参数是 Runnable ,这是一个函数式接口,不允许返回值。当你需要异步操作且不关心返回结果的时候可以使用 runAsync() 方法。

supplyAsync() 方法接受的参数是 Supplier<U> ,这也是一个函数式接口,U 是返回结果值的类型。当你需要异步操作且关心返回结果的时候,可以使用 supplyAsync() 方法。

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> System.out.println("Hello CompletableFuture"));
future.join();// 输出 "Hello CompletableFuture"
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Hello CompletableFuture");
String str = future2.join(); // str的值就是return返回的值,即 "Hello CompletableFuture"
2.3.2 指定自定义线程池

在调用runAsyncsupplyAsync方法的时候,
使用没有指定Executor的方法时,内部使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
默认情况下CompletableFuture会使用公共的ForkJoinPool线程池,如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,进而影响整个系统的性能。所以,强烈建议根据不同的业务类型创建不同的线程池,以避免互相干扰 

另外CompletableFuture是否使用默认线程池的依据和机器的CPU核心数有关。当CPU核心数-1大于1时,才会使用默认的线程池,否则将会为每个CompletableFuture的任务创建一个新线程去执行。

也就是说,CompletableFuture的默认线程池,只有在双核以上的机器内才会使用。在双核及以下的机器中,会为每个任务创建一个新线程,等于没有使用线程池,且有资源耗尽的风险。

因此建议,在使用CompletableFuture时,务必要自定义线程池。因为即便是用到了默认线程池,池内的核心线程数,也为机器核心数-1。也就意味着假设你是4核机器,那最多也只有3个核心线程,对于CPU密集型的任务来说倒还好,但是我们平常写业务代码,更多的是IO密集型任务,对于IO密集型的任务来说,这其实远远不够用的,会导致大量的IO任务在等待,导致吞吐率大幅度下降,即默认线程池比较适用于CPU密集型任务。 

2.3.3  获取任务结果

获取CompletableFuture任务的结果通常由两种方式。使用join()或get()方法,他们都会阻塞当前线程,直到任务完成并返回结果。
join()方法和get()方法非常相似,但join()方法不会抛出InterruptedExceptionExecutionException异常,而是将异常包装在CompletionException中抛出。因此,它更适合在 Lambda 表达式或流式操作中使用。

2.3.4 多任务组合 allOf()

 可以通过 CompletableFuture 的 allOf()这个静态方法来并行运行多个 CompletableFuture 。实际项目中,我们经常需要并行运行多个互不相关的任务,这些任务之间没有依赖关系,可以互相独立地运行。比说我们要处理 3个任务,这 3 个任务都是没有执行顺序依赖的任务,但是我们需要返回给用户的时候将这几个文件的处理的结果进行统计整理。像这种情况我们就可以使用并行运行多个 CompletableFuture 来处理。示例代码如下:

CompletableFuture task1 = CompletableFuture.runAsync(() -> System.out.println("task1"));

CompletableFuture task2 = CompletableFuture.runAsync(() -> System.out.println("task2"));

CompletableFuture task3 = CompletableFuture.runAsync(() -> System.out.println("task3"));

CompletableFuture jobs = CompletableFuture.allOf(task1,task2, task3);

jobs.join();

boolean done = jobs.isDone();

System.out.println("是否执行完毕" + done);

这里有一点需要注意CompletableFuture.allOf() 方法是异步执行的,因此,如果你在调用 allOf() 之后立即调用 isDone() 方法,可能会得到 false,因为 allOf() 方法返回的 CompletableFuture 对象可能还没有完成。所以allOf方法一般配合join(推荐)get使用,阻塞线程,直到全部任务都执行完毕。

3、CompletableFuture使用建议

使用自定义的线程池,CompletableFuture 默认使用 ForkJoinPool.commonPool() 作为线程池,这个线程池是全局共享的,可能会被其他任务占用,导致性能下降。因此,在使用CompletableFuture来执行异步任务的时候,自己定义线程池,可以提高并发度和灵活性,在抛出异常的时候也能快速定位问题。

自定义线程池可以看这篇文章:自定义线程池

  • 39
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一言^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值