java futuretask 源码解析_Java异步编程——深入源码分析FutureTask
Java的异步编程是一项非常常用的多线程技术。
之前通过源码详细分析了ThreadPoolExecutor《你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识》。通过创建一个ThreadPoolExecutor,往里面丢任务就可以实现多线程异步执行了。
但之前的任务主要倾向于线程池,并没有讲到异步编程方面的内容。本文将通过介绍Executor+Future框架(FutureTask是实现的核心),来深入了解下Java的异步编程。
万事从示例开始,我们先通过示例Demo有一个直观的印象,再深入去了解概念与原理。
使用示例
Demo:
Java异步编程——深入源码分析FutureTask
使用上比较简单,
运行结果:
任务1异步执行:0
任务2异步执行:0
任务2异步执行:1
...
任务2异步执行:45
同步代码
任务2异步执行:24
...
任务1异步执行:199
任务1:执行完成
...
任务2异步执行:199
任务2:执行完成
假若你多次执行这个程序,会发现结果大大的不一样,因为两个任务和同步代码是异步由多条线程执行的,打印的结果当然是随机的。
回顾这个Demo做了什么,
构建了一个线程池
往线程池里面丢两个需要执行的任务
最后获取这两个任务的结果
其中第二点是异步执行两个任务,这两个任务和主线程分别是用了三个线程并发执行的,第三点是在主线程中同步等待两个任务的结果。
很容易看出来,异步编程的好处就在于可以让不相干的任务异步执行,不阻塞主线程。若是主线程需要异步执行的结果,此时再去等待结果会更加高效,提高程序的执行效率。
下面来看看整个流程的实现原理。
源码分析
一般在实际项目中,都会有配置有自己的线程池,建议大家在用异步编程时,配置一个专用的线程池,做好线程隔离,避免异步线程影响到其他模块的工作。Demo中为了方便,直接调用Exectors的方法生成一个临时的线程池,日常不建议使用。
我们从这个ExecutorService.submit()方法入手,看看整体实现。
Java异步编程——深入源码分析FutureTask
ExecutorService.submit()定义一个接口。这个接口接收一个Callable参数(执行的任务),返回一个Future(计算结果)。
Callable,相当于一个需要执行的任务。它不接收任何参数,可以返回结果,可以抛出异常。相类似的还有Runnable,它也是不接收,不同点在于它不返回结果,也不抛异常,异常需要在任务内部处理。总结来说Callable更像一个方法的调用,Runnable则是一个不需要理会结果的调用。在JDK 8以后,它们都可以通过Lamda表达式写法去替代内部类的写法(详见Demo)。
Future,一个异步计算的结果。调用get()方法可以得到对应的计算结果,如果调用时没有异步计算完,会阻塞等待计算的结果。同时它还提供方法可以尝试取消任务的执行。
看回ExecutorService.submit()的实现,代码在实现类AbstractExecutorService中。
Java异步编程——深入源码分析FutureTask
除了它接口的实现,还提供了两种变形。原来接口只接收Callable参数,实现类中还新增了接收Runnable参数的。
如果看过之前写的《你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识》,应该了解ThreadPoolExecutor执行任务是可以调用execute()方法的。而这里面submit()方法则是为Callable/Runnable加多一层FutureTask,从而
使执行结果有一个存放的地方,同时也添加一个可以取消的功能。原本的execute()只能执行任务,不会返回结果的,具体实现原理可以看看之前的文章分析。
FutureTask是RunnableFuture的实现。而RunnableFuture是继承Future和Runnable接口的,定义run()接口。
Java异步编程——深入源码分析FutureTask
因为FutureTask有run()接口,所以可以直接用一个Callable/Runnable创建一个FutureTask单独执行。但这样并没有异步的效果,因为没有启用新的线程去跑,而是在原来的线程阻塞执行的。
到这里我们清楚知道了,submit()方法重点是利用Callable/Runnable创建一个FutureTask,然后多线程执行run()方法,达到异步处理并且得到结果的效果。而FutureTask的重点则是run()方法如何持有保存计算的结果。
FutureTask.run()
Java异步编程——深入源码分析FutureTask
首先判断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()的实现。
Java异步编程——深入源码分析FutureTask
两者的基本流程一样,CAS置换状态,保存结果在outcome变量道中,但setException()保存的结果类型固定是Throwable。另外一个不同在于最终state状态,一个是EXCEPTION,一个是NORMAL。
这两个方法最后都调用了finishCompletion()。这个方法主要是配合线程池唤醒下一个任务。
FutureTask.get()
从上面run()方法得知,最后执行的结果放在了outcome变量中。那最终怎么从其中取出结果来,我们来看看get()方法。
Java异步编程——深入源码分析FutureTask
从源码可知,get()方法分两步。第一步,先判断状态,如果计算为完成,则需要阻塞地等待完成。第二步,如果完成了,则调用report()方法获取结果并返回。
先看看awaitDone()阻塞等待完成。该方法可以选用超时功能。
Java异步编程——深入源码分析FutureTask
在自旋的for()循环中,
先判断是否线程被中断,中断的话抛异常退出。
然后开始判断运行的state值,如果state大于COMPLETING,证明计算已经是终态了,此时返回终态变量。
若state等于COMPLETING,证明已经开始计算,并且还在计算中。此时为了避免过多的CPU时间放在这个for循环的自旋上,程序执行Thread.yield(),把线程从运行态降为就绪态,让出CPU时间。
若以上状态都不是,则证明state为NEW,还没开始执行。那么程序在当前循环现在会新增一个WaitNode,在下一个循环里面调用LockSupport.park()把当前线程阻塞。当run()方法结束的时候,会再次唤醒此线程,避免自旋消耗CPU时间。
如果选用了超时功能,在阻塞和自旋过程中超时了,则会返回当前超时的状态。
第二步的report()方法比较简单。
Java异步编程——深入源码分析FutureTask
如果状态是NORMAL,正常结束的话,则把outcome变量返回;
如果是取消或者中断状态的,则抛出取消异常;
如果是EXCEPTION,则把outcome当作异常抛出(之前setException()保存的类型就是Throwable)。从而整个get()会有一个异常抛出。
总结
至此我们已经比较完整地了解Executor+Future的框架原理了,而FutureTask则是该框架的主要实现。下面总结下要点
Executor.sumbit()方法异步执行一个任务,并且返回一个Future结果。
submit()的原理是利用Callable创建一个FutureTask对象,然后执行对象的run()方法,把结果保存在outcome中。
调用get()获取outcome时,如果任务未完成,会阻塞线程,等待执行完毕。
异常和正常结果都放在outcome中,调用get()获取结果或抛出异常。
更多技术文章、精彩干货,请关注
博客:zackku.com
微信公众号:Zack说码
Java异步编程——深入源码分析FutureTask
©著作权归作者所有:来自51CTO博客作者Zack说码的原创作品,如需转载,请注明出处,否则将追究法律责任
java futuretask 源码解析_Java异步编程——深入源码分析FutureTask相关推荐
- BAT高级架构师合力熬夜15天,肝出了这份PDF版《Android百大框架源码解析》,还不快快码住。。。
前言 为什么要阅读源码? 现在中高级Android岗位面试中,对于各种框架的源码都会刨根问底,从而来判断应试者的业务能力边际所在.但是很多开发者习惯直接搬运,对各种框架的源码都没有过深入研究,在面试时 ...
- java.lang 源码剖析_java.lang.Void类源码解析
在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerException if the parent argument is {@code ...
- Framework 源码解析知识梳理(5) startService 源码分析
一.前言 最近在看关于插件化的知识,遇到了如何实现Service插件化的问题,因此,先学习一下Service内部的实现原理,这里面会涉及到应用进程和ActivityManagerService的通信, ...
- java socket 重连复用_Java Socket编程基础及深入讲解(示例代码)
Socket是Java网络编程的基础,了解还是有好处的, 这篇文章主要讲解Socket的基础编程.Socket用在哪呢,主要用在进程间,网络间通信.本篇比较长,特别做了个目录: 一.Socket通信基 ...
- 5. Jetpack源码解析---ViewModel基本使用及源码解析
截止到目前为止,JetpackNote源码分析的文章已经有四篇文章了,这一系列的文章我的初衷是想仔细研究一下Jetpack,最终使用Jetpack组件写一个Demo,上一篇已经分析了LiveData, ...
- Java 8中的并行和异步编程
并行代码是在多个线程上运行的代码,曾经是许多经验丰富的开发人员的噩梦,但是Java 8带来了许多更改,这些更改应该使这种提高性能的技巧更加易于管理. 并行流 在Java 8之前,并行(或并发)代码与顺 ...
- Java中如何使用非阻塞异步编程——CompletableFuture
分享一波:程序员赚外快-必看的巅峰干货 对于Node开发者来说,非阻塞异步编程是他们引以为傲的地方.而在JDK8中,也引入了非阻塞异步编程的概念.所谓非阻塞异步编程,就是一种不需要等待返回结果的多线程 ...
- 源码 解析_最详细集合源码解析之ArrayList集合源码解析
从今天开始我会将集合源码分析陆陆续续整理,写成文章形成集合源码系列文章,方便大家学习 ArrayList集合源码其实相对比较简单,整个源码结构相对于HashMap等源码要好理解的多:先来看下Array ...
- 源码解析2-GUI-绘制引擎(QPainter源码分析 )
Qt源码解析 索引 Qt图形用户界面 应用程序窗口 Qt GUI 模块中最重要的类是QGuiApplication和QWindow.想要在屏幕上显示内容的 Qt 应用程序需要使用这些.QGuiAppl ...
最新文章
- Mysql 分页语句Limit用法
- STM32中GPIO的8种模式
- 计算机科学班(原acm班),计算机科学创新实验班(以下简称ACM班)培养计划.doc
- 网络安装Centos x64 6.10
- 谈我的“先做人,再做技术人员,最后做程序员”
- 【整理】史上最强的娱乐大餐———九奔、汉澳、器普。。。。。。
- 想和人脑一样智能? IBM 的芯片级模仿才是关键
- c语言最长公共子序列_序列比对(二十四)——最长公共子序列
- 实验:添加AXI IP到设计
- 软件测试的测试数据分析,软件测试结果归纳与分析
- 资本纷纷入局,咖啡赛道还能香多久?
- 关键词搜索查找工具-批量关键词查找搜索工具-根据关键词自动采集素材软件
- char * 与char []区别总结
- Android:自定义View实现绚丽的圆形进度条
- simpleCaptcha语音验证码实现及其扩展
- 企业微信可以取消实名认证吗?如何操作
- SQL SELECT(复杂查询) 之 等值查询 内外连接
- 机器学习:决策树-基础算法,剪枝,连续值缺失值处理,多变量决策树(附代码实现)
- smartlook.tasktray.exe无法找到入口
- 无线充电 Qi认证流程详解
热门文章
- 破解Navicat for MySql
- Webpack 实现 Tree shaking 的前世今生
- 假如苹果构建了一个搜索引擎
- 三机齐发!五大全球首发的“安卓机皇”4999元起,“安卓之光” 5999元起
- 编程网站 Perl.com 被劫,售价 19 万美元
- 九问国产操作系统,九大掌门人万字回应!
- 我发现了个 Python 黑魔法,执行任意代码都会自动念上一段「平安经」
- 从鲁班造木鸢到智能控制,图解世界无人机发展简史
- NASA 遭攻击,安全 Bug 仍未解决!
- 除了敲代码,程序员还需要哪些必备技能?