>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 29866 个阅读者 刷新本主题
 * 贴子主题:  CompletableFuture实现非阻塞的原理 回复文章 点赞(0)  收藏  
作者:javathinker    发表时间:2019-10-03 21:08:54     消息  查看  搜索  好友  复制  引用

1.Future接口
1.1 什么是Future?
在jdk的官方的注解中写道

在上面的 注释 中我们能知道Future用来代表异步的结果,并且提供了检查计算完成,等待完成,检索结果完成等方法。简而言之就是提供一个异步运算结果的一个建模。它可以让我们把耗时的操作从我们本身的调用 线程 中释放出来,只需要完成后再进行回调。就好像我们去饭店里面吃饭,不需要你去煮饭,而你这个时候可以做任何事,然后饭煮好后就会回调你去吃。

1.2 JDK8以前的Future
在JDK8以前的Future使用比较简单,我们只需要把我们需要用来异步计算的过程封装在Callable或者Runnable中,比如一些很耗时的操作(不能占用我们的调用线程 时间 的),然后再将它提交给我们的 线程池 Executor Service 。 代码 例子如下:

上面展示了我们的线程可以 并发 方式调用另一个线程去做我们耗时的操作。当我们必须依赖我们的异步结果的时候我们就可以调用get方法去获得。当我们调用get方法的时候如果我们的任务完成就可以立马返回,但是如果任务没有完成就会阻塞,直到超时为止。

Future底层是怎么实现的呢? 我们首先来到我们ExecutorService的代码中submit方法这里会返回一个Future

在sumbmit中会对我们的Callable进行包装封装成我们的FutureT ask ,我们最后的Future其实也是Future的实现类FutureTask,FutureTask实现了Runnable接口所以这里直接调用execute。在FutureTask代码中的run方法代码如下:

可以看见当我们执行完成之后会set(result)来通知我们的结果完成了。set(result)代码如下:

首先用CAS置换状态为完成,以及替换结果,当替换结果完成之后,才会替换为我们的最终状态,这里主要是怕我们设置完COMPLETING状态之后最终值还没有真正的赋值出去,而我们的get就去使用了,所以还会有个最终状态。我们的get()方法的代码如下:

首先获得当前状态,然后判断状态是否完成,如果没有完成则进入awaitDone循环等待,这也是我们阻塞的代码,然后返回我们的最终结果。

1.2.1缺陷
我们的Future使用很简单,这也导致了如果我们想完成一些复杂的任务可能就比较难。比如下面一些例子:

将两个异步计算合成一个异步计算,这两个异步计算互相独立,同时第二个又依赖第一个的结果。

当Future集合中某个任务最快结束时,返回结果。

等待Future结合中的所有任务都完成。

通过编程方式完成一个Future任务的执行。

应对Future的完成时间。也就是我们的回调通知。

1.3Comple tab leFuture
CompletableFuture是JDK8提出的一个支持非阻塞的多功能的Future,同样也是实现了Future接口。

1.3.1CompletableFuture基本实现
下面会写一个比较简单的例子:

用法上来说和Future有一点不同,我们这里fork了一个新的线程来完成我们的异步操作,在异步操作中我们会设置值,然后在外部做我们其他操作。在complete中会用CAS替换result,然后当我们get如果可以获取到值得时候就可以返回了。

1.3.2错误处理
上面介绍了正常情况下但是当我们在我们异步线程中产生了错误的话就会非常的不幸,错误的异常不会告知给你,会被扼杀在我们的异步线程中,而我们的get方法会被阻塞。

对于我们的CompletableFuture提供了completeException方法可以让我们返回我们异步线程中的异常,代码如下:

在我们新建的异步线程中直接New一个异常抛出,在我们客户端中依然可以获得异常。

1.3.2工厂方法创建CompletableFuture
我们的上面的代码虽然不复杂,但是我们的 java 8依然对其提供了大量的工厂方法,用这些方法更容易完成整个流程。如下面的例子:

上面的例子通过工厂方法supplyAsync提供了一个Completable,在异步线程中的输出是ForkJoinPool可以看出当我们不指定线程池的时候会使用ForkJoinPool,而我们上面的compelte的操作在我们的run方法中做了,源代码如下:

上面代码中通过d.completeValue(f.get());设置了我们的值。同样的 构造方法 还有runasync等等。

1.3.3计算结果完成时的处理
当CompletableFuture计算结果完成时,我们需要对结果进行处理,或者当CompletableFuture产生异常的时候需要对异常进行处理。有如下几种方法:

上面的四种方法都返回了CompletableFuture,当我们 Action 执行完毕的时候,future返回的值和我们原始的CompletableFuture的值是一样的。上面以Async结尾的会在新的线程池中执行,上面没有一Async结尾的会在之前的CompletableFuture执行的线程中执行。例子代码如下:

exceptionally方法返回一个新的CompletableFuture,当原始的CompletableFuture抛出异常的时候,就会触发这个CompletableFuture的计算,调用function计算值,否则如果原始的CompletableFuture正常计算完后,这个新的CompletableFuture也计算完成,它的值和原始的CompletableFuture的计算的值相同。也就是这个exceptionally方法用来处理异常的情况。

1.3.4计算结果完成时的转换
上面我们讨论了如何计算结果完成时进行的处理,接下来我们讨论如何对计算结果完成时,对结果进行转换。

这里同样也是返回CompletableFuture,但是这个结果会由我们自定义返回去转换他,同样的不以Async结尾的方法由原来的线程计算,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池 executor 运行。Java的CompletableFuture类总是遵循这样的原则,下面就不一一赘述了。 例子代码如下:

上面的最终结果会输出11,我们成功将其用两个then App ly转换为String。

1.3.5计算结果完成时的消费
上面已经讲了结果完成时的处理和转换,他们最后的CompletableFuture都会返回对应的值,这里还会有一个只会对计算结果消费不会返回任何结果的方法。

函数接口为Consumer,就知道了只会对函数进行消费,例子代码如下:

这个方法用法很简单我就不多说了.Accept家族还有个方法是用来合并结果当两个CompletionS tag e都正常执行的时候就会执行提供的action,它用来组合另外一个异步的结果。

runAfterBoth是当两个CompletionStage都正常完成计算的时候,执行一个Runnable,这个Runnable并不使用计算的结果。 示例代码如下:

CompletableFuture也提供了执行Runnable的办法,这里我们就不能使用我们future中的值了。

1.3.6对计算结果的组合
首先是介绍一下连接两个future的方法:

对于Compose可以连接两个CompletableFuture,其内部处理逻辑是当第一个CompletableFuture处理没有完成时会合并成一个CompletableFuture,如果处理完成,第二个future会紧接上一个CompletableFuture进行处理。

我们上面的thenAcceptBoth讲了合并两个future,但是没有返回值这里将介绍一个有返回值的方法,如下:

例子比较简单如下:

上面介绍了两个future完成的时候应该完成的工作,接下来介绍任意一个future完成时需要执行的工作,方法如下:

上面两个是一个是纯消费不返回结果,一个是计算后返回结果。

1.3.6其他方法
allOf方法是当所有的CompletableFuture都执行完后执行计算。

anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。

1.3.7建议
CompletableFuture和Java8的Stream搭配使用对于一些并行访问的耗时操作有很大的性能提高,可以自行了解。


程序猿的技术大观园:www.javathinker.net
  Java面向对象编程-->Lambda表达式
  JavaWeb开发-->Servlet技术详解(Ⅰ)
  JSP与Hibernate开发-->Java应用分层架构及软件模型
  Java网络编程-->Socket用法详解
  精通Spring-->Vue组件开发高级技术
  Vue3开发-->Vue指令
  [原创]汽車美容
  CRMEB_Java新零售社交电商系统
  java的三种随机数生成方式
  快速理解 函数式编程,响应式编程,链式编程
  JDK自带JVM调优工具的用法
  java 中文繁简体转换工具 opencc4j
  Java并发之volatile关键字内存可见性问题
  Java设计模式:组合模式
  Java入门实用代码:100以内整数求和运算
  Java 入门实用代码:设置文件只读
  Java入门实用代码:打印矩形
  Java 入门实用代码:汉诺塔算法
  Java入门实用代码:格式化时间(SimpleDateFormat)
  Java 入门实用代码:取最大和最小值
  java零基础入门-面向对象篇 抽象类
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。