实战,让学习更简单     全国咨询热线:400-009-1906

北京 切换校区

全国24小时免费热线

400-009-1906

Java异步编程——深入源码分析FutureTask

时间:2018-12-05   来源:尚学堂   阅读:74
首页> Java异步编程——深入源码分析FutureTask

        Java的异步编程是一项非常常用的多线程技术。

        本文将通过介绍Executor+Future框架(FutureTask是实现的核心),来深入了解下Java的异步编程。

        万事从示例开始,我们先通过示例Demo有一个直观的印象,再深入去了解概念与原理。

 

        使用示例

        Demo:
        使用上比较简单,运行结果:

任务1异步执行:0
任务2异步执行:0
任务2异步执行:1
...
任务2异步执行:45
同步代码
任务2异步执行:24
...
任务1异步执行:199
任务1:执行完成
...
任务2异步执行:199
任务2:执行完成


       假若你多次执行这个程序,会发现结果大大的不一样,因为两个任务和同步代码是异步由多条线程执行的,打印的结果当然是随机的。

        回顾这个Demo做了什么:

        1.构建了一个线程池

        2.往线程池里面丢两个需要执行的任务

        3.最后获取这两个任务的结果

        其中第二点是异步执行两个任务,这两个任务和主线程分别是用了三个线程并发执行的,第三点是在主线程中同步等待两个任务的结果。

        很容易看出来,异步编程的好处就在于可以让不相干的任务异步执行,不阻塞主线程。若是主线程需要异步执行的结果,此时再去等待结果会更加高效,提高程序的执行效率。

        下面来看看整个流程的实现原理。

 

        源码分析

        一般在实际项目中,都会有配置有自己的线程池,建议大家在用异步编程时,配置一个专用的线程池,做好线程隔离,避免异步线程影响到其他模块的工作。Demo中为了方便,直接调用Exectors的方法生成一个临时的线程池,日常不建议使用。

        我们从这个ExecutorService.submit()方法入手,看看整体实现。

       ExecutorService.submit()定义一个接口。这个接口接收一个Callable参数(执行的任务),返回一个Future(计算结果)。

       Callable,相当于一个需要执行的任务。它不接收任何参数,可以返回结果,可以抛出异常。相类似的还有Runnable,它也是不接收,不同点在于它不返回结果,也不抛异常,异常需要在任务内部处理。总结来说Callable更像一个方法的调用,Runnable则是一个不需要理会结果的调用。在JDK 8以后,它们都可以通过Lamda表达式写法去替代内部类的写法(详见Demo)。

       Future,一个异步计算的结果。调用get()方法可以得到对应的计算结果,如果调用时没有异步计算完,会阻塞等待计算的结果。同时它还提供方法可以尝试取消任务的执行。

        看回ExecutorService.submit()的实现,代码在实现类AbstractExecutorService中。
        除了它接口的实现,还提供了两种变形。原来接口只接收Callable参数,实现类中还新增了接收Runnable参数的。

ThreadPoolExecutor执行任务是可以调用execute()方法的。而这里面submit()方法则是为Callable/Runnable加多一层FutureTask,从而使执行结果有一个存放的地方,同时也添加一个可以取消的功能。原本的execute()只能执行任务,不会返回结果的。

FutureTaskRunnableFuture的实现。而RunnableFuture是继承FutureRunnable接口的,定义run()接口。        因为FutureTaskrun()接口,所以可以直接用一个Callable/Runnable创建一个FutureTask单独执行。但这样并没有异步的效果,因为没有启用新的线程去跑,而是在原来的线程阻塞执行的。

        到这里我们清楚知道了,submit()方法重点是利用Callable/Runnable创建一个FutureTask,然后多线程执行run()方法,达到异步处理并且得到结果的效果。而FutureTask的重点则是run()方法如何持有保存计算的结果。

 

        FutureTask.run()

        首先判断futureTask对象的state状态,如果不是NEW的话,证明已经开始运行过了,则退出执行。同时futureTask对象通过CAS,把当前线程赋值给变量runner(是Thread类型,说明对象使用哪个线程执行的),如果CAS失败则退出。

        外层try{}代码块中,对callable判空和state状态必须是NEW。内层try{}代码真正调用callable,开始执行任务。若执行成功,则把ran变量设为true,保存结果在result变量中,证明已跑成功过了;若抛异常了,则设为false,result为空,并且调用setException()保存异常。最后如果ran为true的话,则调用set()保存result结果。

        看下setException()set()的实现。
        两者的基本流程一样,CAS置换状态,保存结果在outcome变量道中,但setException()保存的结果类型固定是Throwable。另外一个不同在于最终state状态,一个是EXCEPTION,一个是NORMAL。

        这两个方法最后都调用了finishCompletion()。这个方法主要是配合线程池唤醒下一个任务。

 

        FutureTask.get()

        从上面run()方法得知,最后执行的结果放在了outcome变量中。那最终怎么从其中取出结果来,我们来看看get()方法。

        从源码可知,get()方法分两步。第一步,先判断状态,如果计算为完成,则需要阻塞地等待完成。第二步,如果完成了,则调用report()方法获取结果并返回。

        先看看awaitDone()阻塞等待完成。该方法可以选用超时功能。
        在自旋的for()循环中,先判断是否线程被中断,中断的话抛异常退出。

  •         然后开始判断运行的state值,如果state大于COMPLETING,证明计算已经是终态了,此时返回终态变量。
  •         state等于COMPLETING,证明已经开始计算,并且还在计算中。此时为了避免过多的CPU时间放在这个for循环的自旋上,程序执行Thread.yield(),把线程从运行态降为就绪态,让出CPU时间。
  •         若以上状态都不是,则证明stateNEW,还没开始执行。那么程序在当前循环现在会新增一个WaitNode,在下一个循环里面调用LockSupport.park()把当前线程阻塞。当run()方法结束的时候,会再次唤醒此线程,避免自旋消耗CPU时间。
  • 如果选用了超时功能,在阻塞和自旋过程中超时了,则会返回当前超时的状态。

        第二步的report()方法比较简单。

  •         如果状态是NORMAL,正常结束的话,则把outcome变量返回;
  •         如果是取消或者中断状态的,则抛出取消异常;
  •         如果是EXCEPTION,则把outcome当作异常抛出(之前setException()保存的类型就是Throwable)。从而整个get()会有一个异常抛出。

        总结

        至此我们已经比较完整地了解Executor+Future的框架原理了,而FutureTask则是该框架的主要实现。下面总结下要点

        1.Executor.sumbit()方法异步执行一个任务,并且返回一个Future结果。

        2.submit()的原理是利用Callable创建一个FutureTask对象,然后执行对象的run()方法,把结果保存在outcome中。

        3.调用get()获取outcome时,如果任务未完成,会阻塞线程,等待执行完毕。

        4.异常和正常结果都放在outcome中,调用get()获取结果或抛出异常。

相关资讯

  • 北京校区
  • 山西校区
  • 郑州校区
  • 武汉校区
  • 四川校区
  • 长沙校区
  • 深圳校区
  • 上海校区
  • 广州校区
  • 保定招生办

北京海淀区校区(总部):北京市海淀区西三旗街道建材城西路中腾建华商务大厦东侧二层尚学堂
北京京南校区:北京亦庄经济开发区科创十四街6号院1号楼 赛蒂国际工业园
咨询电话:400-009-1906 / 010-56233821
面授课程: JavaEE培训大数据就业班培训大数据云计算周末班培训零基础大数据连读班培训大数据云计算高手班培训人工智能周末班培训人工智能+Python全栈培训H5+PHP全栈工程师培训

山西学区地址:山西省晋中市榆次区大学城大学生活广场万科商业A1座702
咨询电话:0354-3052381 / 18903441162

郑州学区地址:金水区东风路经三路北100米注协大厦10楼
咨询电话:0371-55177956 

武汉学区地址:湖北省武汉市江夏区江夏大道26号 宏信悦谷创业园4楼
咨询电话:027-87989193

四川学区地址:成都市高新区锦晖西一街99号布鲁明顿大厦2栋1003室
咨询电话:028-65176856 / 13880900114

长沙学区地址:长沙市岳麓区麓源路湖南电子商务基地5楼(公交车站:六沟垅)
咨询电话:0731-83072091

深圳校区地址:深圳市宝安区航城街道航城大道航城创新创业园A4栋210(固戍地铁站C出口)
咨询电话:0755-23061965 / 18898413781

上海尚学堂松江校区地址:上海市松江区荣乐东路2369弄45号绿地伯顿大厦2层
咨询电话:021-67690939

广州校区地址:广州市天河区元岗横路31号慧通产业广场B区B1栋6楼尚学堂(地铁3号线或6号线到“天河客运站”D出口,右拐直走约800米)
咨询电话:020-2989 6995

保定招生办公室

地址:河北省保定市竞秀区朝阳南大街777号鸿悦国际1101室

电话:15132423123

Copyright 2006-2018 北京尚学堂科技有限公司  京ICP备13018289号-19  京公网安备11010802015183  
媒体联系:18610174079 闫老师  

有位老师想和您聊一聊