您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
聊一聊多线程不得不知的Future(一)
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
聊一聊多线程不得不知的Future(一)
自猿其说Tech
2021-07-08
IP归属:未知
353440浏览
Flutter
计算机编程
### 一、前言(引子) 在并发编程中,我们经常用到非阻塞的模型,在之前的多线程的三种实现中,不管是继承thread类还是实现runnable接口,都无法保证获取到之前的执行结果。通过实现Callback接口,并用Future可以来接收多线程的执行结果。 Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。 举个例子:比如去吃早点时,点了包子和凉菜,包子需要等3分钟,凉菜只需1分钟,如果是串行的一个执行,在吃上早点的时候需要等待4分钟,但是因为你在等包子的时候,可以同时准备凉菜,所以在准备凉菜的过程中,可以同时准备包子,这样只需要等待3分钟。那Future这种模式就是后面这种执行模式。 **经常会使用到Future的场景有:** ① 计算密集场景 ② 处理大数据量 ③远程方法调用等 **下面是我在WMS入库模块中找到的使用线程池的代码:** ![](//img1.jcloudcs.com/developer.jdcloud.com/38b59375-6c4d-4857-a49b-c2fb3287143e20210708111235.png) 在这里,针对上面代码,我先抛出几个问题: ①为什么不直接使用JDK自带的线程池? ②使用Google并发包中的线程池有什么好处?解决了什么问题? 答案就隐藏在下边的内容之中。 ### 二、初级版--jdk中的Future Future是java 1.5引入的一个interface,Future接口定义了主要的5个接口方法,有RunnableFuture和SchedualFuture继承这个接口,以及CompleteFuture和ForkJoinTask继承这个接口。 ![](//img1.jcloudcs.com/developer.jdcloud.com/7df47556-178e-490b-a28b-e1ce55cd63c320210708111308.png) 废话不多说,直接看一段代码(Future的简单使用): ![](//img1.jcloudcs.com/developer.jdcloud.com/541004de-4bdc-4867-8bce-f61f2531d35220210708111330.png) 从上面的代码我们可以看出,当我们想要返回值的时候,都需要调用下面的这个 get() 方法: ![](//img1.jcloudcs.com/developer.jdcloud.com/8f1eb3f3-3e25-4169-80f7-5654daf8605220210708111352.png) 而从这个方法的描述可以看出,这是一个阻塞方法。拿不到值就在那里等着。当然,还有一个带超时时间的 get 方法,等指定时间后就不等了。 总之就是有可能要等的。只要等,那么就是阻塞。只要是阻塞,就是一个假异步(阉割版)。 所以总结一下这种场景下返回的 Future 的不足之处: 只有主动调用 get 方法去获取值,但是有可能值还没准备好,就阻塞等待。 任务处理过程中出现异常会把异常隐藏,封装到 Future 里面去,只有调用 get 方法的时候才知道异常了。 **那么什么才是真正的异步?** 好莱坞原则:Don't Call Us,We'll Call you! 接下来,让我们见识一下真正的异步。 ### 三、Guava的Future 我们先来看下Guava的Future的类图: ![](//img1.jcloudcs.com/developer.jdcloud.com/affac525-013d-42b2-9b0c-c6d0403c5aa020210708111424.png) **真正的异步其实是回调(callback):**说到回调,那么就需要在异步任务提交之后,注册一个回调函数就行。 Google 提供的 Guava 包里面对 JDK 的 Future 进行了扩展: ![](//img1.jcloudcs.com/developer.jdcloud.com/8b998f2f-492c-42b7-bc25-88851d88a8ab20210708111442.png) 新增了一个 addListenter 方法,入参是一个 Runnable 的任务类型和一个线程池。 使用方法如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/2a01cd53-c718-4bec-aeb8-5bb4b34dc61320210708111524.png) ①创建线程池的方法有所改变: ![](//img1.jcloudcs.com/developer.jdcloud.com/29c0a3df-18ae-47b3-a3ac-5eebd87fb0c620210708111555.png) - 用Guava 里面的 MoreExecutors 方法装饰了一下。 ②用装饰后的 executor 调用 submit 方法,就会返回 ListenableFuture ,拿到这个 ListenableFuture 之后,在上面注册监听: ![](//img1.jcloudcs.com/developer.jdcloud.com/3c2b1302-19d1-4518-bf2c-6df84c99d8bb20210708111619.png) 仔细观察这个输出的结果: ![](//img1.jcloudcs.com/developer.jdcloud.com/ca000811-ea93-45a2-b08f-ac04fe04e45820210708111642.png) 从运行结果可以看出来:获取运行结果是在另外的线程里面执行的,完全没有阻塞主线程。 ③上面的案例中我们使用的是addListener的方式,其实还有另外一种--FutureCallback 。 使用方法如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/0c0f002b-1290-4ac4-a2bb-fabc8586a5ed20210708111712.png) 有 onSuccess 和 onFailure 两个Callback方法。 ![](//img1.jcloudcs.com/developer.jdcloud.com/1dbdab25-863c-45c8-867e-9c078cfd232720210708111729.png) 输出的结果如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/f004d8fe-1a1d-4e22-82fc-7d9ad744771120210708111753.png) ### 四、终极版--CompletableFuture 在前边我们讲到的Future是JDK5的产物: 经过发展,Doug Lea 老爷子在 JDK8 里面引入了新的 CompletableFuture : ![](//img1.jcloudcs.com/developer.jdcloud.com/335fa9db-64b9-416e-86c3-c067c763626720210708111817.png) 异步,JDK8这才是真正的异步。 我们来看下CompletableFuture的类图: ![](//img1.jcloudcs.com/developer.jdcloud.com/b3df1cf5-ce12-4dcc-b6c8-99ba6bdc8f6820210708111835.png) CompletableFuture 实现了两个接口,一个是我们熟悉的 Future ,一个是 CompletionStage。 CompletionStage接口,可以把这个接口理解为一个任务的某个阶段。所以多个 CompletionStage 链接在一起就是一个任务链。前一个任务完成后,下一个任务就会自动触发。 使用方法如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/44ea4af8-8edf-45b0-a148-ef61306268bd20210708111855.png) 输出结果如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/38a46443-a4dd-4bbe-882f-50eb6ef7ad6420210708111915.png) 从输出结果可以看出来,是异步执行的,ForkJoinPool.commonPool() 是其默认使用的线程池。 ![](//img1.jcloudcs.com/developer.jdcloud.com/b15fbd32-f984-4d9b-9787-4089caddd4ee20210708111931.png) 其实,我们也是可以自己指定线程池的: ![](//img1.jcloudcs.com/developer.jdcloud.com/a5d551c6-3e06-4147-8916-73ee7ab2437e20210708111953.png) 接下来主要看看 CompletableFuture 对于异常的处理。非常的优雅。 不需要 try-catch 代码块包裹,也不需要调用 Future.get() 才知道异常了,它提供了一个 handle 方法,可以处理上游异步任务中出现的异常: ![](//img1.jcloudcs.com/developer.jdcloud.com/6aff733c-45de-451f-aaee-1d83037c6ec020210708112018.png) ### 五、CompletableFuture 在Dubbo中的使用 从v2.7.0开始,Dubbo的所有异步编程接口开始以CompletableFuture为基础。在这里简单的说一下使用方法: 服务端接口的定义要改变,主要指返回值类型: ![](//img1.jcloudcs.com/developer.jdcloud.com/af6779d7-287b-4ed6-a6a1-9a8fd717650120210708112059.png) Consumer端引用服务: ![](//img1.jcloudcs.com/developer.jdcloud.com/9e57c43d-f3ac-4be3-a5a9-8dacb145ba2920210708112118.png) 调用发起后,立刻得到一个future。然后通过Lamda表达式向此future增加回调。将远程接口真正返回后,由此回调处理返回结果。 这里有一个问题没讲明白,回调的执行必然不是当前线程,Consumer部应该也会配置一个线程池,由线程池里边的线程执行回调应该。 上边的异步调用没有涉及到RpcContext,另外一个异步调用的方法就是使用RpcContext,与上边方法的区别在于Consumer端的配置与代码,这种风格与旧版本非常像。 配置: ![](//img1.jcloudcs.com/developer.jdcloud.com/069cad1d-b49e-4cfc-8c5c-6765d1348b8b20210708112141.png) 代码: ![](//img1.jcloudcs.com/developer.jdcloud.com/68ac4576-7f86-4027-a4ee-c31423631bc220210708112202.png) ### 六、总结与个人理解 在前言中提出的两个问题,答案就在上面的讲解之中。其实这一切说明的是:java在发展,技术在发展。 **个人理解:** 众所周知,JDK8推出了很多的新特性和功能(Lambda、FunctionInterface、CompletableFuture等),Why?其实仔细看,Lambda表达式很像JS中的箭头函数,从上面的讲解中,也看到从1.5的Future,到Guava的Future,再到1.8的CompletableFuture,其实在1.5的Future推出后,受到了很大的吐槽,这种阻塞,假异步是存在很大的问题的,所以才有了之后GoogleGuava的Future,Doug Lea老爷子也是意识到了这个问题,所以才在JDK8中推出了强大的CompletableFuture,这也说明了:每一种技术的出现都是为了解决某些问题、处理某些业务而出现的,所以,作为java的一名开发者,是幸运的,技术会越来越好,也会越来越助于我们的开发工作。 ------------ ###### 自猿其说Tech-JDL京东物流技术发展部 ###### 作者:网规技术部 乔杰
原创文章,需联系作者,授权转载
上一篇:架构研究:研发敏捷与中台架构(论前台bp研发敏捷)
下一篇:Hive 迁移参考方案及测试
相关文章
【技术干货】企业级扫描平台EOS关于JS扫描落地与实践!
Flutter异步编程中Completer的使用
聊一聊多线程不得不知的Future(一)
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号