异步神器 CompletableFuture


前言

在JDK1.8之前,我们使用的Java多线程编程,主要创建方式有两种,继承Thread类或者实现Runnable接口,但是这两种方式有个弊端,无法获取到线程执行的结果。今天和大家分享在JDK1.8中提供的一种更加强大的异步编程类CompletableFuture,来解决之前的疼点问题。


一、CompletableFuture概述

CompletableFuture提供了更加强大的异步编程api,它实现了Future接口和CompletionStage接口,是对前者的丰富扩展,弥补了获取执行结果的局限性;同时CompletionStage实现了对任务的编排能力。
CompletableFuture相比于Future最大的改进就是提供了回调监听的功能,轻松的组织不同任务的执行顺序和执行方式,可以将上一任务的执行结果作为下一任务的条件,很方便的获取每一次任务的执行结果,在某种程度上讲,这项能力是它的核心能力。

二、CompletableFuture的常用api

1. 创建异步任务

1.1 supplyAsync

supplyAsync是创建带有返回值的异步任务。它有如下两个方法,一个是使用默认线程池(ForkJoinPool.commonPool())的方法,一个是带有自定义线程池的重载方法

// 带返回值异步请求,默认线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
 
// 带返回值的异步请求,可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

1.2 runAsync

runAsync是创建没有返回值的异步任务。它有如下两个方法,一个是使用默认线程池(ForkJoinPool.commonPool())的方法,一个是带有自定义线程池的重载方法

// 不带返回值的异步请求,默认线程池
public static CompletableFuture<Void> runAsync(Runnable runnable)
 
// 不带返回值的异步请求,可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

ForkJoinPool线程池默认创建的线程数是 CPU 的核数。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰

2. 获取任务执行结果

2.1 get

用来获取CompletableFuture异步之后的返回值,抛出的是经过检查的异常。

// 如果完成则返回结果,否则就抛出具体的异常
public T get() throws InterruptedException, ExecutionException 
 
// 最大时间等待返回结果,否则就抛出具体异常
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException

2.2 join

用来获取CompletableFuture异步之后的返回值,抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。

// 完成时返回结果值,否则抛出unchecked异常。为了更好地符合通用函数形式的使用,如果完成此 CompletableFuture所涉及的计算引发异常,则此方法将引发unchecked异常并将底层异常作为其原因
public T join()

3. 异步回调处理

3.1 thenApply和thenApplyAsync

thenApply 表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中。
thenAccep和thenAccepAsync区别在于,使用thenAccep方法时子任务与父任务使用的是同一个线程,而thenAccepAsync在子任务中可能是另起一个线程执行任务,并且thenAccepAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。

3.2 thenAccept和thenAcceptAsync

thenAccep表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,无返回值。
thenAccep和thenAccepAsync区别在于,使用thenAccep方法时子任务与父任务使用的是同一个线程,而thenAccepAsync在子任务中可能是另起一个线程执行任务,并且thenAccepAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。

3.3 thenRun和thenRunAsync

thenRun表示某个任务执行完成后执行的动作,即回调方法,无入参,无返回值。
thenRun和thenRunAsync区别在于,使用thenRun方法时子任务与父任务使用的是同一个线程,而thenRunAsync在子任务中可能是另起一个线程执行任务,并且thenRunAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。

4. 多任务组合处理

4.1 allOf

用来实现多任务的并行执行,当所有任务完成时返回一个新的CompletableFuture。经常结合get或join方法使用,阻塞主线程,主线程需要等待所有任务异步执行完毕,才会继续往下执行。只要有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("future1完成!");
    return "future1完成!";
});

CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
    System.out.println("future2完成!");
});

//将创建的所有异步任务加入到CompletableFuture中
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
//阻塞主线程,等待全部异步任务执行完毕后唤醒主线程
all.join();

4.2 anyOf

用来实现多任务的并行执行,当其中某一个任务完成时返回一个新的CompletableFuture。经常结合get或join方法使用,阻塞主线程,主线程只要等待所有任务的其中一个执行完毕,就会继续往下执行。如果其中一个任务抛出异常,不会影响anyOf的执行,如果都是正常执行,则get返回执行完成任务的结果。

三、CompletableFuture使用需要注意点

因为使用CompletableFuture的程序相当于引入了多线程或异步,我们首先需要想到的是当前task由哪个Thread来执行,使用不当容易引起性能问题,可以根据方法命名做区分:
xxxx():表示该方法将继续在当前执行CompletableFuture的方法线程中执行
xxxxAsync():表示异步,在线程池的其它线程中执行


结束语

本文只列举了CompletableFuture类中一少部分比较常用的api方法,这些方法都是在生产中实际用过的,还有很多可以用在不同场景下的方法,朋友们可以去Java官方中文API文档中自行研究。

简单的分享,希望能帮助更多初学者,同时欢迎大家的指正和交流。如果觉着本文有用的话,记着点赞和收藏!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值