乐趣区

关于java:笑了面试官问我知不知道异步编程的Future

荒腔走板

大家好,我是 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,一个被代码耽搁的文学创作者,不是大佬,然而喜爱分享,是一个又暖又有料的四川好男人。

还有,重要的事件说三遍:欢送关注我呀。欢送关注我呀。欢送关注我呀。

退出移动版