荒腔走板
大家好,我是 why,欢送来到我间断周更优质原创文章的第 60 篇。
老规矩,先来一个简短的荒腔走板,给寒冷的技术文注入一丝色调。
下面这图是我五年前,在学校宿舍拍的。
前几天因为有点事件,关上了多年没有关上的 QQ。而后忽然推送了一个“那年今日”发送的动静。
这张图片就是那个动静外面的。
2015 年 8 月的时候正是大三放暑假的工夫,然而那个寒假我找了一个实习,所以寒假期间住在学校外面。宿舍就我一个人。那个时候我齐全没有意识到,这是我程序猿生涯的一个真正的开始,也是我学生时代提前结束的宣告。
8 月 5 日凌晨,一只小猫忽然蹿到了宿舍外面,在宿舍外面目中无人的,像宿管阿姨一样审查着所有货色。甚至间接跳到桌子上,看着我敲代码。齐全不怕我的样子。
于是我把它放到了我的自行车上,当模特拍了几张照片。
初见这只小猫时的那种惊喜我还历历在目,然而这波回顾杀给我的更大的冲击是:原来,这件事曾经过来五年了。
如果没有 QQ 的这个揭示,你让我想这件事是产生在什么时候的,我的第一反馈必定是好多年前的事件了吧,缓缓咂摸之后有可能才想起,原来是大三寒假的时候的事件,而后再认真一算,原来是仅仅五年前的事件呀。
短短的五年怎么产生了怎么多事件啊,把这五年塞的满满当当的。
不晓得为什么如果把人生求学、步入社会的各个阶段离开来看,我每次回头望的时候都感觉这如同是他人的故事啊。
幸好我本人记录了下来,幸好这真的是我本人的故事。
好了,说回文章。
你就是写了个假异步
先去我的第一篇公众号文章中拿张图片:《Dubbo 2.7 新个性之异步化革新》
这是 rpc 的四种调用形式:
文本次要分享这个 future 的调用形式,不讲 Dubbo 框架,这里只是一个引子。
谈到 future 的时候大家都会想到异步编程。然而你认真看框起来这里:
客户端线程调用 future.get() 办法的时候还是会阻塞以后线程的。
我倒是感觉这充其量只能算一个阉割版的异步编程。
本文将带你从阉割版的 future 聊到升级版的 Google Guava 的 future,最初谈谈加强版的 future。
先聊聊线程池的提交形式
谈到 Future 的时候,咱们基本上就会想到线程池,想到它的几种提交形式。
先是最简略的,execute 形式提交,不关怀返回值的,间接往线程池外面扔工作就完事:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
public class JDKThreadPoolExecutorTest {
能够看一下 execute 办法,承受一个 Runnable 办法,返回类型是 void:
而后是 submit 办法。你晓得线程池有几种 submit 办法吗?
尽管你常常用,然而可能你素来没有关怀过人家。呸,渣男:
有三种 submit。这三种依照提交工作的类型来算分为两个类型。
- 提交执行 Runnable 类型的工作。
- 提交执行 Callable 类型的工作。
然而返回值都是 Future,这才是咱们关怀的货色。
兴许你晓得线程池有三种 submit 办法,然而兴许你基本不晓得外面的工作分为两种类型,你就只晓得往线程池外面扔,也不论扔的是什么类型的工作。
咱们先看一下 Callable 类型的工作是怎么执行的:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
public class JDKThreadPoolExecutorTest {
这里利用 lambda 表达式,间接在工作体外面带上一个返回值,这时你看调用的办法就变成了这个:
运行后果也能拿到工作体外面的返回了。输入后果如下:
好,接下来再说说 submit 的工作为 Runable 类型的状况。
这个时候有两个重载的模式:
标号为 ① 的办法扔进去一个 Runable 的工作,返回一个 Future,而这个返回的 Future,相当于是返回了一个寂寞。上面我会说到起因。
标号为 ② 的办法扔进去一个 Runable 的工作的同时,再扔进去一个泛型 T,而巧好返回的 Future 外面的泛型也是 T,那么咱们大胆的猜想一下这就是同一个对象。如果是同一个对象,阐明咱们能够一个对象传到工作体外面去一顿操作,而后通过 Future 再次拿到这个对象的。一会就去验证。
来,先验证标号为 ① 的办法,我为啥说它返回了一个寂寞。
首先,还是先把测试案例放在这里:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
public class JDKThreadPoolExecutorTest {
能够看到,的确是调用的标号为 ① 的办法:
同时,咱们也能够看到 future.get() 办法的返回值为 null。
你说,这不是返回了一个寂寞是干啥?
当你想用标号为 ① 的办法时,我劝你间接用 execute 形式提交工作。还不须要构建一个寂寞的返回值,徒增无用对象。
接下来,咱们看看标号为 ② 的办法是怎么用的:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
public class JDKThreadPoolExecutorTest {
能够看到革新之后,的确是调用了标号为 ② 的办法:
future.get() 办法的输入值也是异步工作中咱们通过计算后得出的 5201314。
你看,渣男就是这样,明明不懂你,还非得用花言巧语来轰炸你。呸。
好了。综上,线程池的提交形式一共有四种:一种 execute,无返回值。三种 submit,有返回值。
submit 中依照提交工作的类型又分为两种:一个是 Callable,一个是 Runable。
submit 中 Runable 的工作类型又有两个重载办法:一个返回了个寂寞,一个返回了个渣男。哦,不。一个返回了个寂寞,一个返回了个对象。
这个时候就有人要站进去说:你说的不对,你就是瞎说,明明就只有 execute 这一种提交形式。
是的,“只有 execute 这一种提交形式”这一种说法也是没错的。
请看源码:
三种 submit 办法外面调用的都是 execute 办法。
能把后面这些办法娓娓道来,从外表谈到外在的这种人,才是坏蛋。
只有爱你,才会把你钻研透。
当然,还有这几种提交形式,用的不多,就不开展说了:
写到这里我不禁想起了我的第三篇文章,真是奇怪的工夫线开始膨胀了的感觉,《有的线程它死了,于是它变成一道面试题》,这篇文章外面聊到了不同提交形式,对于异样的不同解决形式。
我就问你:一个线程池中的线程异样了,那么线程池会怎么解决这个线程?
你要是不晓得,能够去看看这篇文章,毕竟,有可能在面试的时候遇到的:
好,下面这些货色捋分明了之后。咱们再聚焦到返回值 Future 上:
从下面的代码咱们能够看出,当咱们想要返回值的时候,都须要调用上面的这个 get() 办法:
而从这个办法的形容能够看出,这是一个阻塞办法。拿不到值就在那里等着。当然,还有一个带超时工夫的 get 办法,等指定工夫后就不等了。
呸,渣男。没急躁,这点工夫都舍不得等。
总之就是有可能要等的。只有等,那么就是阻塞。只有是阻塞,就是一个假异步。
所以总结一下这种场景下返回的 Future 的不足之处:
- 只有被动调用 get 办法去获取值,然而有可能值还没筹备好,就阻塞期待。
- 工作处理过程中出现异常会把异样暗藏,封装到 Future 外面去,只有调用 get 办法的时候才晓得异样了。
写到这里的时候我不禁想起一个形象的例子,我给你举一个。
假如你想约你的女神一起去吃饭。女神嘛,必定是要先画个美美的妆才会进来逛街的。而女神化妆就能够类比为咱们提交的一个异步工作。
假如你是一个小屌丝,那么女神就会对你说:我曾经开始化妆了,你到楼下了就给我打电话。
而后你就拾掇行头筹备登程,这就是你提交异步工作后还能够做一些本人的事件。
你花了一小时到了女神楼下,打电话给她:女神你好,我到你楼下了。
女神说:你先等着吧,我的妆还没画好呢。
于是你开始期待,无尽的期待。这就是不带超时工夫的 future.get() 办法。
也有可能你硬气一点,对女神说:我最多再等 24 小时哈,超过 24 小时不下楼,我就走了。
这就是带超时工夫的 future.get(timeout,unit) 办法:
后果 24 小时之后,女神还没下来,你就走了。
当然,还有一种状况就是你到楼下给女神打电话,女神说:哎,明天我男神约我进来看电影,就不和你去吃饭了哈。原本我想提前给你说的,然而我又记不起你电话,只有你打过去我能力通知你。就这样,你本人玩去吧。
这就相当于异步工作执行过程中抛出了异样,而你只有在调用了 get 办法(打电话操作)之后才晓得原来异样了。
而真正的异步是你不必等我,我好了我就叫你。
就像女神接到男神的电话时说的:我须要一点工夫筹备一下,你先玩本人的吧,我一会好了给你打电话。
这让我想起了好莱坞准则:Don’t Call Us,We’ll Call you!
接下来,让咱们见识一下真正的异步。
什么叫真正的:“你先玩本人的,我一会好了叫你。”
Guava 的 Future
女神说的:“好了叫你”。
就是一种回调机制。说到回调,那么咱们就须要在异步工作提交之后,注册一个回调函数就行。
Google 提供的 Guava 包外面对 JDK 的 Future 进行了扩大:
新增了一个 addListenter 办法,入参是一个 Runnable 的工作类型和一个线程池。
应用办法,先看代码:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
public class JDKThreadPoolExecutorTest {
首先创立线程池的形式变了,须要用 Guava 外面的 MoreExecutors 办法装璜一下:
- ounter(line
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
而后用装璜后的 executor 调用 submit 办法(任意一种),就会返回 ListenableFuture,拿到这个 ListenableFuture 之后,咱们就能够在下面注册监听:
所以,下面的程序咱们调用的是入参为 callable 类型的接口:
从运行后果能够看进去: 获取运行后果是在另外的线程外面执行的,齐全没有阻塞主线程 。
和之前的“假异步”还是有很大区别的。
除了下面的 addListener 办法外,其实我更喜爱用 FutureCallback 的形式。
能够看一下代码,十分的直观:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
public class JDKThreadPoolExecutorTest {
有 onSuccess 办法和 onFailure 办法。
下面的程序输入后果为:
如果异步工作执行的时候抛出了异样,比方女神被她的男神约走了,异步工作改成这样:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
ListenableFuture<String> listenableFuture = executor.submit(() -> {
最终的运行后果就是这样:
是的,女神去看电影了。她肯定只是不想吃饭而已。
加强版的 Future – CompletableFuture
第一大节讲的 Future 是 JDK 1.5 时代的产物:
通过了这么多年的倒退,Doug Lea 在 JDK 1.8 外面引入了新的 CompletableFuture:
到了 JDK 1.8 时代,这才是真正的异步编程。
CompletableFuture 实现了两个接口,一个是咱们相熟的 Future,一个是 CompletionStage。
CompletionStage 接口,你看这个接口的名称中有一个 Stage:
能够把这个接口了解为一个工作的某个阶段。所以多个 CompletionStage 链接在一起就是一个工作链。前一个工作实现后,下一个工作就会主动触发。
CompletableFuture 外面的办法十分的多。
因为篇幅起因,我就只演示一个办法:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
public class JDKThreadPoolExecutorTest {
该办法的执行后果如下:
咱们执行的时候并没有指定用什么线程池,然而从后果能够看到也是异步的执行。
从输入日志中是能够看出端倪的,ForkJoinPool.commonPool() 是其默认应用的线程池。
当然,咱们也能够本人指定。
这个办法在很多开源框架外面应用的还是十分的多的。
接下来次要看看 CompletableFuture 对于异样的解决。我感觉十分的优雅。
不须要 try-catch 代码块包裹,也不须要调用 Future.get() 才晓得异样了,它提供了一个 handle 办法,能够解决上游异步工作中呈现的异样:
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
- ounter(line
public class JDKThreadPoolExecutorTest {
因为女神在化妆的时候,接到男神的电话约她看电影,就只能放你鸽子了。
所以,下面程序的输入后果如下:
如果,你顺利把女神约进去了,是这样的:
好了,女神都约进去了,文章就到这里了。去干闲事吧。
最初说一句(求关注)
依照我的教训,女神约进去了你须要筹备好答复一个问题:
你看我明天有什么不同?
首先这题就是一道送命题,答复到她预期的答案的概率十分的低。有可能她明天不一样的中央就是换了一个指甲油、换了一个美瞳、换了一个耳环之类的。
很显著,这些十分细节的中央咱们很难发现。然而别怂。
先含情脉脉的认真的盯着她,花一分钟找答案,一分钟后没有找到答案,就说:
你每天都不一样,每天都比昨天更加漂亮。
好了,满腹经纶,难免会有纰漏,如果你发现了谬误的中央,还请你在后盾留言指出来,我对其加以批改。
感谢您的浏览,我保持原创,非常欢送并感谢您的关注。
我是 why,一个被代码耽搁的文学创作者,不是大佬,然而喜爱分享,是一个又暖又有料的四川好男人。
还有,重要的事件说三遍:欢送关注我呀。欢送关注我呀。欢送关注我呀。