大家好,我是why,欢迎收看我连续几周更优质原创文章的第60篇。两周前写的关于我眼中的成都的文章还在发酵,初步估计全网阅读量应该在100w以上。诚恳又忐忑,但需要说明的是,我其实是一名技术博主,偶尔会马虎写一些生活相关的东西,所以本文回归技术。像往常一样,让我们??以简短、野蛮的语气开始,为冰冷的技术文本注入一抹色彩。上图是五年前在我学校宿舍拍的。前几天因为有事,打开了多年没打开的QQ。然后突然推送了一个“那年的今天”的帖子。这张照片是动态的。2015年8月,是大三的暑假,但是那个暑假我找到了实习,所以暑假就住在学校。我一个人在宿舍。那时的我完全没有意识到,这才是我真正的程序员生涯的开始,也是学生时代提前结束的宣告。8月5日凌晨,一只小猫突然跳进了宿舍。宿舍里没有其他人,像个管宿舍的阿姨一样审视着一切。甚至直接跳到桌子上看我敲代码。完全不害怕我的样子。所以我把它放在我的自行车上并模仿了几张照片。我还记得第一次见到这只小猫时的惊喜,但这波记忆对我的影响更大:原来这件事已经过去五年了。如果没有QQ的提醒,你让我怀疑这件事是什么时候发生的。我的第一反应一定是很多年前。可能只有慢慢摸了才会记起来。原来是大三的暑假。当时发生的事情,再仔细一算,原来也不过是五年前的事情。短短五年,怎么会发生这么多事情?它充满了我的五年。不知道为什么如果分开来看人生的各个阶段,学习和步入社会,每走过一个阶段,再回头看,都觉得这是别人的故事。幸运的是,我一年一年地记录下来。幸运的是,这真的是我自己的故事。好了,回到文章。你刚刚写了一个假的异步。以上就是rpc的四种调用方式:文中主要分享这个future的调用方式,不讲dubbo框架。这只是一个介绍。说到future,大家都会想到异步编程。但是仔细看这里的框架:当客户端线程调用future.get()方法时,当前线程还是会被阻塞。我认为这充其量是一个阉割版的异步编程。本文将带你从Future的阉割版到GoogleGuavaFuture的升级版,最后说说Future的增强版。先说线程池的提交方式。提到Future,我们基本上就会想到线程池和它的几种提交方式。第一种最简单,以execute方式提交,如果不关心返回值,直接把task扔进线程池就完了:publicclassJDKThreadPoolExecutorTest{publicstaticvoidmain(String[]args)throwsException{ThreadPoolExecutorexecutor=newThreadPoolExecutor(2,5,60,TimeUnit.SECONDS,newLinkedBlockingQueue<>(10));//执行(Runnablecommand)方法。无返回值executor.execute(()->{System.out.println("Focusonwhytechnology");});Thread.currentThread().join();}}可以看看execute方法,accept一个Runnable方法,返回类型为void:然后是submit方法。你知道线程池有几种提交方法吗?虽然你经常使用它,但你可能永远不会关心别人。呸,没心没肺的人:提交分三种。这三种类型根据提交任务的类型分为两种。提交任务以执行Runnable类型。提交执行Callable类型的任务。但是返回值都是Future,这才是我们关心的。可能你知道线程池有3个submit方法,但是你可能不知道里面有两种任务,你只知道怎么扔到线程池里,不管你扔什么类型的任务。我们来看看Callable类型的任务是如何执行的:=executor.submit(()->{System.out.println("Focusonwhytechnology");return"这次必须!";});System.out.println("Futurecontent:"+future.get());Thread.currentThread().join();}}这里使用了lambda表达式,直接在任务体中带上一个返回值。这时候可以看到调用方法变成了这样:运行结果也是我在任务体中可以拿到return。输出结果如下:好了,说说提交任务是Runable类型的情况。这时候有两种重载形式:标注①的方法抛出一个Runable任务,返回一个Future,返回的Future相当于返回一个孤单。下面我会说为什么。标有②的方法抛出一个Runable任务,同时抛出一个泛型T,正好返回的Future中的泛型也是T,所以我们大胆猜猜这是同一个对象。如果是同一个对象,那就意味着我们可以把一个对象传递给任务体进行一次操作,然后再通过Future获取这个对象。待会验证。来吧,先验证标①的方法,为什么我说它返回一个寂寞。首先把测试用例放在这里:.submit(()->{System.out.println("Focusonwhytechnology");});System.out.println("Futurecontent:"+future.get());Thread.currentThread().join();}}可以看出确实调用了标注①的方法:同时,我们也可以看到future.get()方法的返回值为null。你说,还一个寂寞的目的是什么?当你要使用标①的方法时,我建议你直接在execute方法中提交任务。不需要建立一个孤独的返回值,那样会增加无用的对象。接下来我们看看标有②的方法是如何使用的:;Futurefuture=executor.submit(()->{System.out.println("Focusonwhytechnology");//计算逻辑在这里atomicInteger.set(5201314);},atomicInteger);System.out.println("futurecontent:"+future.get());Thread.currentThread().join();}}可以看到经过改造后,标为②的方法确实调用了:future的输出值.get()方法也是我们在异步任务中计算出来的5201314。你看,没心没肺的人就是这样。他明明不理解你,非要用甜言蜜语轰炸你。呸。好吧。总结一下,线程池的提交方式有四种:一种是execute,没有返回值。三种有返回值的提交。根据submit中提交任务的类型,分为两种:一种是Callable,一种是Runable。submit中的Runable任务类型有两个重载方法:一个返回一个孤独的人,一个返回一个没心没肺的人。不好了。一个返回孤独,一个返回对象。这时候就会有人站出来说:你说的不对,你胡说八道,明明只有execute这个提交方式。是的,“提交方法只有execute”这句话也是对的。请看源码:三个submit方法中都调用了execute方法。能从表面到里详细解释以上方法的人,就是好人。只有爱你,才会把你研究透彻。当然还有这几种投稿方式,用的不多,就不展开了:写到这里不禁想起了我的第三篇文章。感觉时间线开始变短了,感觉怪怪的。在这篇文章中,我们谈到了不同的提交方式和异常的不同处理方式。请问:如果一个线程池中的一个线程出现异常,线程池将如何处理这个线程?如果你不知道,你可以阅读这篇文章。毕竟面试的时候可能会遇到:OK,把上面的东西都整理好了。我们重点关注Future的返回值:从上面的代码可以看出,当我们想要返回一个值的时候,需要调用下面的get()方法:而从这个方法的描述可以看出这是一种拦截方式。如果取不到值,就在那里等着。当然也有带超时的get方法,超过指定时间就不再等待了。呸,没良心的人。我性急了,不忍心等到这点时间。简而言之,可以等待。等一下,然后它就阻塞了。只要是阻塞的,就是假异步。所以总结一下这个场景下返回的Future的缺点:只有主动调用get方法获取值,但是有可能值没有准备好,所以会阻塞等待。当任务处理过程中出现异常时,异常会被隐藏起来封装在一个Future中。只有调用了get方法才会知道异常。写到这里,不由得想到一个生动的例子,举个例子吧。假设你想请你的女神与你共进晚餐。女神,出门逛街一定要化个美妆哦。女神妆可以比作我们提交的一个异步任务。假设你是个小屌丝,那女神就会对你说:我开始化妆了,你下楼给我打电话。然后你收拾行装准备出发,这时你可以在提交异步任务后做一些你自己的事情。你花了一个小时才到女神楼下,你喊她:女神你好,我在楼下。女神说:你等一下,我的妆还没化呢。所以你开始等待,无休止地等待。这是没有超时的future.get()方法。也有可能你可以再倔强一点,跟女神说:我最多再等24小时。如果超过24小时不下楼,我就走。这是有超时的future.get(timeout,unit)方法:24小时后,女神还没有下来,你就离开。当然,还有一种情况,你把女神叫到楼下,女神说:哎,我家男神今天约我出去看电影,就不跟你吃饭了。本来想提前告诉你的,可是我记不住你的电话号码。我只能告诉你,如果你打电话。就这样吧,自己玩吧。这相当于在异步任务执行过程中抛出异常,调用get方法(call操作)后才知道原来的异常。而真正的异步是你不用等我,我准备好了就给你打电话。就像女神接到男神电话时说的:我需要一点时间准备,你先玩玩自己,等我准备好了再给你打电话。这让我想起了好莱坞的原则:不要打电话给我们,我们会打电话给你!接下来,让我们看看真正的异步。什么是真实的:“先自己玩,等我准备好了再叫你。”番石榴的未来女神说:“我准备好了就叫你”。它是一种回调机制。说到回调,那么我们需要在异步任务提交后注册一个回调函数。Google提供的Guava包扩展了JDK的Future:新增了一个addListenter方法,入参为一个Runnable任务类型和一个线程池。如何使用,先看代码:.out.println(Thread.currentThread().getName()+"-女神:我开始化妆了,我给你打电话。");TimeUnit.SECONDS.sleep(5);return"化妆完毕.";});listenableFuture.addListener(()->{try{System.out.println(Thread.currentThread().getName()+"-未来内容:"+listenableFuture.get());}catch(Exceptione){e.printStackTrace();}},executor);System.out.println(Thread.currentThread().getName()+"-女神化妆的时候你自己做吧。");Thread.currentThread().join();}}首先,创建线程池的方式变了,需要用Guava中的MoreExecutors方法修饰一下:ListeningExecutorServiceexecutor=MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());然后调用用修饰过的executor方法(任意一个)提交,会返回ListenableFuture,拿到ListenableFuture后,我们就可以在上面注册监听器:所以,在上面的程序中,我们调用入参为callable类型的接口:从运行结果可以看出:获取运行结果是在另外一个线程中执行的,完全不会阻塞主线程,与以前的“假异步”。除了上面的addListener方法,其实我更喜欢用FutureCallback方法。可以看看代码,很直观:{System.out.println(Thread.currentThread().getName()+"-女神:开始化妆了,我给你打电话。");TimeUnit.SECONDS.sleep(5);return"化妆完成。";});Futures.addCallback(listenableFuture,newFutureCallback(){@OverridepublicvoidonSuccess(@NullableStringresult){System.out.println(Thread.currentThread().getName()+"-futurecontent:"+result);}@OverridepublicvoidonFailure(Throwablet){System.out.println(Thread.currentThread().getName()+"-女神让你鸽了。");t.printStackTrace();}});System.out.println(Thread.currentThread().getName()+"-女神化妆时你可以自己做自己的事情。");Thread.currentThread().join();}}有onSuccess方法一ndonFailure方法。上面程序的输出是:如果异步任务执行过程中抛出异常,比如女神被男神请走了,异步任务改成这样:ListenableFuturelistenableFuture=executor。submit(()->{System.out.println(Thread.currentThread().getName()+"-女神:我开始化妆了,我给你打电话。");TimeUnit.SECONDS.sleep(5);thrownewException("男神约我看电影,不陪你吃饭。");});最后的运行结果是这样的:是的,女神去看电影了。她一定只是不想吃东西。Future的增强版——CompletableFuture第一节提到的Future是JDK1.5时代的产物:经过这么多年的发展,DougLea在JDK1.8中引入了一个新的CompletableFuture:在JDK1.8时代,这才是真正的异步编程。CompletableFuture实现了两个接口,一个是大家熟悉的Future,一个是CompletionStage。CompletionStage接口,可以看到这个接口的名字里有一个Stage:这个接口可以理解为一个任务的某个阶段。所以多个CompletionStages被链接在一起形成一个任务链。上一个任务完成后,会自动触发下一个任务。CompletableFuture中有很多方法。由于篇幅原因,我只演示一种方法:getName()+”-女神:我开始化妆了,画完了我给你打电话。");try{TimeUnit.SECONDS.sleep(5);}catch(InterruptedExceptione){e.printStackTrace();}return"化妆完成。";});completableFuture.whenComplete((returnStr,exception)->{if(exception==null){System.out.println(Thread.currentThread().getName()+returnStr);}else{System.out.println(Thread.currentThread().getName()+"女神让你走。");exception.printStackTrace();}});System.out.println(Thread.currentThread().getName()+"-女神化妆的时候你可以做你自己的事情。");Thread.currentThread().join();}}这个方法的执行结果如下:我们没有指定什么时候使用什么线程池我们执行了,但是从结果可以看出也是异步执行的,从输出日志可以看出ForkJoinPool.commonPool()是默认使用的线程池,当然我们也可以自己指定.这个方法在很多开源框架中还是有很多的,接下来看CompletableFuture对异常的处理,我觉得很优雅,不需要try-catch代码块包裹,也不需要打电话给未来。get()知道异常。它提供了一个handle方法,可以处理上游异步任务中出现的异常:()+”-女神:我开始化妆了,我给你打个电话。");thrownewRuntimeException("大神让我去看电影,下次再见,你是个好人。");}).handleAsync((result,exception)->{if(exception!=null){System.out.println(Thread.currentThread().getName()+"-女神让你鸽了!");returnexception.getCause();}else{returnresult;}}).thenApplyAsync((returnStr)->{System.out.println(Thread.currentThread().getName()+"-"+returnStr);returnreturnStr;});System.out.println(Thread.currentThread().getName()+"-当女神正在化妆可以自己做。");Thread.currentThread().join();}}因为女神在化妆的时候接到了男神的电话约她看电影,我只能让你走所以,上面程序的输出结果如下:如果,你已经成功约到女神了,是这样的:好了,女神已经约会了,文章就到这里,我们开始吧致企业。本文转载自微信公众号“何为科技”,可通过关注以下二维码。转载本文请联系为什么科技公众号。