非阻塞异步编程
所谓非阻塞异步编程方法,简单地说,就是不用等待返回结果的多线程的回调方法的封装。相比于阻塞式异步回调方法,非阻塞异步编程方法使用一个监听器,这样在使用回调的过程中,就能够自动感知到调用结果,从而实现了高并发的调用方法。
使用阻塞式调用,会有大量的时间耗费在等待之中。Java 开发语 Java1.8 之后 ,提供了非阻塞的回调处理方法,它封装在CompletableFuture 类中。CompletableFuture 提供了非常强大的程序扩展能力可以帮助我们简化异步编程的复杂性,并通过非阻塞的回调方式处理调用结果。 CompletableFuture 不但提供了非阻塞的异步调用的方法,还可以将处理结果进行转换和结合使用。
场景
比如有这样的业务场景:
在用户信息查询接口中需要返回:用户名称、性别、等级、头像、积分、成长值等信息。
而用户名称、性别、等级、头像在用户服务中,积分在积分服务中,成长值在成长值服务中。为了汇总这些数据统一返回,需要另外提供一个对外接口服务。
于是,用户信息查询接口需要调用用户查询接口、积分查询接口 和 成长值查询接口,然后汇总数据统一返回。
调用过程如下图所示:
调用远程接口总耗时 530ms = 200ms + 150ms + 180ms
显然这种串行调用远程接口性能是非常不好的,调用远程接口总的耗时为所有的远程接口耗时之和。
那么如何优化远程接口性能呢?
上面说到,既然串行调用多个远程接口性能很差,为什么不改成并行呢?
如下图所示:
调用远程接口总耗时 200ms = 200ms(即耗时最长的那次远程接口调用)
API 方法分类
创建类
- completeFuture 可以用于创建默认返回值
- runAsync 异步执行,无返回值
- supplyAsync 异步执行,有返回值
- anyOf 任意一个执行完成,就可以进行下一步动作
- allOf 全部完成所有任务,才可以进行下一步任务
状态取值类
- join 合并结果,等待
- get 合并等待结果,可以增加超时时间;get 和 join 区别,join 只会抛出 unchecked 异常,get 会返回具体的异常
- getNow 如果结果计算完成或者异常了,则返回结果或异常;否则,返回 valueIfAbsent 的值
- isCancelled
- isCompletedExceptionally
- isDone
控制类 用于主动控制 CompletableFuture 的完成行为
- complete
- completeExceptionally
- cancel
接续类 用于注入回调行为
- thenApply, thenApplyAsync
- thenAccept, thenAcceptAsync
- thenRun, thenRunAsync
- thenCombine, thenCombineAsync
- thenAcceptBoth, thenAcceptBothAsync
- runAfterBoth, runAfterBothAsync
- applyToEither, applyToEitherAsync
- acceptEither, acceptEitherAsync
- runAfterEither, runAfterEitherAsync
- thenCompose, thenComposeAsync
- whenComplete, whenCompleteAsync
- handle, handleAsync
- exceptionally
记忆规则
以 Async 结尾的方法,都是异步方法,对应的没有 Async 则是同步方法,一般都是一个异步方法对应一个同步方法。
以 Async 后缀结尾的方法,都有两个重载的方法,一个是使用内容的 forkjoin 线程池,一种是使用自定义线程池
以 run 开头的方法,其入口参数一定是无参的,并且没有返回值,类似于执行 Runnable 方法。
以 supply 开头的方法,入口也是没有参数的,但是有返回值
以 Accept 开头或者结尾的方法,入口参数是有参数,但是没有返回值
以 Apply 开头或者结尾的方法,入口有参数,有返回值
带有 either 后缀的方法,表示谁先完成就消费谁
API使用
创建 CompletableFuture
创建 CompletableFuture,其实就是将我们要煮的米饭,委托给电饭煲;要煮米饭,我们要准备这么几件事情,其一我们要制定制作米饭的方式,其二,我们要指定电饭煲。除此之外,我们也可以委托其他的事情,最后可以通过 all 或者 any 进行组合。
1 | // 异步任务,无返回值,采用内部的forkjoin线程池 |
取值与状态
1 | // 常用的是下面的这几种 |
控制 CompletableFuture 执行
1 | // 3种方式: |
接续 CompletableFuture + (Runnable,Consumer,Function)
1 | CompletableFuture future = CompletableFuture.supplyAsync(()->{ |
接续 CompletableFuture + CompletableFuture
假如蒸米饭和、热牛奶、炒菜等已经是 3 个不同的 CompletableFuture,可以使用接续方式,将两个或者多个 CompletableFuture 组合在一起使用。
1 | CompletableFuture rice = CompletableFuture.supplyAsync(()->{ |
接续 CompletableFuture + 处理结果
如果我们只想做结果处理,也没有其他的接续动作,并且我们想要判断异常的情况,那么可以用这种接续方式。
1 | // whenCompleteAsync:处理完成或异常,无返回值 |
SpringBoot使用
线程池配置类
1 | import org.springframework.context.annotation.Bean; |
Service层
1 |
|
案例 1:使用 get 获取返回结果
1 |
|
案例 2:使用 thenApply 方法将结果返回给其它函数调用
1 | //案例2:使用 thenApply 方法将结果返回给其它函数调用 |
说明:使用上面的对象,调用 thenApply 即可将返回结果给其它函数调用。
①如果其它函数需要获取结果,但该函数并不需要返回值,可以定义成 Void,并使用的是 thenAccept 方法如下:
1 | CompletableFuture<Void> thenAccept = supplyAsync.thenAccept(s->{}); |
②如果其它函数既不需要获取结果,也不需要返回值,只想单纯的在前面的方法执行完成后,再执行一段逻辑,可以使用 thenRun。如下:
1 | CompletableFuture<Void> thenRun = supplyAsync.thenRun(()->{}); |
案例 3:使用 thenCompose 链接两个调用
1 | //案例3:使用 thenCompose 链接两个调用 |
测试结果:biandan 说 让天下没有难写的代码
说明:使用 thenCompose 可以将上一个的返回结果给下一个函数使用。
如果要链接可完成的 CompletableFuture 方法,那么最好使用 thenCompose ()
案例 4:使用 thenCombine 将两个结果结合起来,给另一个函数调用
1 | //案例4:使用 thenCombine 将两个结果结合起来,给另一个调用 |
案例 5:allOf 多种结合的调用
1 | //案例5:allof 多种结合的调用 |
注意 CompletableFuture.allOf 的返回类型是 CompletableFuture。这种方法的局限性在于它不能返回所有 Supplier 的组合结果。我们必须从未来手动获取结果。使用 CompletableFuture.join () 方法获取。