并发系列(二)——FutureTask类源码简析
背景
本文基于JDK 11,主要介绍FutureTask类中的run()、get()和cancel() 方法,没有过多解析相应interface中的注释,但阅读源码时建议先阅读注释,明白方法的主要的功能,再去看源码会更快。
文中若有不正确的地方欢迎大伙留言指出,谢谢了!
1、FutureTask类图
1.1 FutureTask简介
FutureTask类图如下(使用IDEA生成)。如图所示,FutureTask实现了Future接口的所有方法,并且实现了Runnable接口,其中,Runnable接口的现实类用于被线程执行,而Future代表的是异步计算的结果。因此,FutureTask类可以理解为,执行run()(实现Runnable接口中的方法),通过Future的get()方法获取结果。

1.2 FutureTask的属性
//任务线程总共有七中状态如下:
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6; /** The underlying callable; nulled out after running */
//在run()方法中调用
private Callable<V> callable;
/** The result to return or exception to throw from get() */
//任务执行结果,callable.call()正常执行的返回值
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
//任务线程
private volatile Thread runner;
/** Treiber stack of waiting threads */
//等待任务结果的线程组成的节点,放在链表对列中
private volatile WaitNode waiters;
2、源码解析
2.1 run()方法
public void run() {
//1、若是任务的状态不是NEW,且使用CAS将runner置为当前线程则直接返回
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
//2、任务不为null,且state的状态为NEW的情况下才执行任务
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//执行任务并接收执行结果
result = c.call();
//正常执行结果则将标识置为true
ran = true;
} catch (Throwable ex) {
//3、任务发生异常,执行或cancel(),则结果置为null,并记录异常信息
result = null;
ran = false;
setException(ex);
}
//4、任务正常结束,则设置返回结果
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
//5、若是异常导致,走另一个流程
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
1)若任务的状态不是NEW,或者使用CAS将runner置为当前线程失败,则直接返回的原因是防止多线程调用;
2)再度确认任务执行的前置条件;
3)任务执行异常,将result置为null,并记录异常,setException()源码如下:
protected void setException(Throwable t) {
//使用CAS将状态置为中间态COMPLETING
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = t;
STATE.setRelease(this, EXCEPTIONAL); // final state
//任务处于结束态时,遍历唤醒等待result的线程
finishCompletion();
}
}
任务的状态变化为NEW - > COMPLETING -> EXCEPTIONAL
4)任务正常结果则会设置result之后,唤醒waitNode的链表对列中等待任务结果的线程;
5)异常后的调用逻辑如下:
//保证调用cancel在run方法返回之前中断执行任务
private void handlePossibleCancellationInterrupt(int s) {
// It is possible for our interrupter to stall before getting a
// chance to interrupt us. Let's spin-wait patiently.
if (s == INTERRUPTING)
//自旋等待
while (state == INTERRUPTING)
//当前线程让出CPU执行权
Thread.yield(); // wait out pending interrupt
}
2.2 get()方法
源码分析如下:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
//等待任务完成
s = awaitDone(false, 0L);
//返回结果
return report(s);
}
其中,等待过程分析如下:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// The code below is very delicate, to achieve these goals:
// - call nanoTime exactly once for each call to park
// - if nanos <= 0L, return promptly without allocation or nanoTime
// - if nanos == Long.MIN_VALUE, don't underflow
// - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
// and we suffer a spurious wakeup, we will do no worse than
// to park-spin for a while
long startTime = 0L; // Special value 0L means not yet parked
WaitNode q = null;
boolean queued = false;
for (;;) {
int s = state;
//1、任务的状态已经处于最终的状态,则将任务线程的引用置为null,直接返回状态
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
//2、任务的状态为COMPLETING说明任务已经接近完成,则当前线程让出CPU权限以便任务执行线程获取到CPU执行权
else if (s == COMPLETING)
// We may have already promised (via isDone) that we are done
// so never return empty-handed or throw InterruptedException
Thread.yield();
//3、当前线程被中断,则将当前线程从等待任务结果的对列中移除,并抛出异常
else if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
//4、任务线程的状态小于COMPLETING,则将当前调用get()方法的线程新建一个Node
else if (q == null) {
if (timed && nanos <= 0L)
return s;
q = new WaitNode();
}
//5、若由当前线程构成的Node未加入链表中,则加入
else if (!queued)
queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
//6、是否开启了超时获取结果
else if (timed) {
final long parkNanos;
if (startTime == 0L) { // first time
startTime = System.nanoTime();
if (startTime == 0L)
startTime = 1L;
parkNanos = nanos;
} else {
long elapsed = System.nanoTime() - startTime;
//7、超时则从栈中移除当前线程
if (elapsed >= nanos) {
removeWaiter(q);
return state;
}
parkNanos = nanos - elapsed;
}
// nanoTime may be slow; recheck before parking
//当前线程挂起
if (state < COMPLETING)
LockSupport.parkNanos(this, parkNanos);
}
else
LockSupport.park(this);
}
}
获取到返回的状态值后,根据其状态值判断是返回结果还是抛出异常。
2.2 cancel()方法
public boolean cancel(boolean mayInterruptIfRunning) {
//1、若任务线程的状态为NEW,则将其状态从NEW置为INTERRUPTING、CANCELLED
if (!(state == NEW && STATE.compareAndSet
(this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
//CAS改变任务线程的状态失败,则直接返回false,表示cancel失败
return false;
try { // in case call to interrupt throws exception
//2、改变任务线程的状态成功后,根据是否中断running的任务线程的标识位,决定是否中断正在运行的任务线程
if (mayInterruptIfRunning) {
try {
Thread t = runner;
//任务线程不为null,则使用interrupt()中断
if (t != null)
t.interrupt();
} finally { // final state
//设置状态
STATE.setRelease(this, INTERRUPTED);
}
}
} finally {
//3、清理等待任务结果的等待线程
finishCompletion();
}
return true;
}
3、总结
1)执行run()方法,是在调用在Callable的call()方法,其实在初始化时被指定;
2)调用get()方法,若是任务线程还在执行,则会把调用get的线程封装成waitNode塞入到FutureTask类内部的阻塞链表对列中,可以有多个线程同时调用get()方法;
3)cancel()方法是通过对任务线程调用interrupt()实现;
并发系列(二)——FutureTask类源码简析的更多相关文章
- Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析
一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...
- django-jwt token校验源码简析
一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...
- SpringMVC学习(一)——概念、流程图、源码简析
学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...
- AFNetworking源码简析
AFNetworking基本是苹果开发中网络请求库的标配,它是一个轻量级的网络库,专门针对iOS和OS X的网络应用设计,具有模块化的架构和丰富的APIs接口,功能强大并且使用简单,深受苹果应用开发人 ...
- 0002 - Spring MVC 拦截器源码简析:拦截器加载与执行
1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日 ...
- DRF之APIView源码简析
一. 安装djangorestframework 安装的方式有以下三种,注意,模块就叫djangorestframework. 方式一:pip3 install djangorestframework ...
- 源码简析XXL-JOB的注册和执行过程
一,前言 XXL-JOB是一个优秀的国产开源分布式任务调度平台,他有着自己的一套调度注册中心,提供了丰富的调度和阻塞策略等,这些都是可视化的操作,使用起来十分方便. 由于是国产的,所以上手还是比较快的 ...
- 源码简析Spring-Integration执行过程
一,前言 Spring-Integration基于Spring,在应用程序中启用了轻量级消息传递,并支持通过声明式适配器与外部系统集成.这一段官网的介绍,概况了整个Integration的用途.个人感 ...
- OpenStack之Glance源码简析
Glance简介 OpenStack镜像服务器是一套虚拟机镜像发现.注册.检索. glance架构图: Glance源码结构: glance/api:主要负责接收响应镜像管理命令的Restful请求, ...
随机推荐
- Zookeeper分布式过程协同技术 - 群首选举
Zookeeper分布式过程协同技术 - 群首选举 群首概念 群首为集群中服务器选择出来的一个服务器,并被集群认可.设置群首目的在与对客户端所发起的状态变更请求进行排序,包括:create.setDa ...
- React源码之组件的实现与首次渲染
react: v15.0.0 本文讲 组件如何编译 以及 ReactDOM.render 的渲染过程. babel 的编译 babel 将 React JSX 编译成 JavaScript. 在 ba ...
- 11. RobotFramework内置库-Collections
Collections库是RobotFramework用来处理列表和字典的库,详细可参见官方介绍. 官方地址:http://robotframework.org/robotframework/late ...
- pycharm中连接MySql出现 Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.的错误解决
具体异常如下: 这个异常特别烦人,是mysql连接驱动的问题,可以用二步解决: 第一步 从网上下载驱动,从mysql-connector-java_8.0.16-1ubuntu16.04_all.de ...
- 每日一题 - 剑指 Offer 32 - I. 从上到下打印二叉树
题目信息 时间: 2019-06-25 题目链接:Leetcode tag:BFS(广度优先搜索) 队列 难易程度:中等 题目描述: 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印 ...
- 整理一下CSS最容易躺枪的二十规则,大家能躺中几条?
整理一下CSS最容易躺枪的二十规则,大家能躺中几条? 转载:API中文网 一.float:left/right 或者 position: absolute 后还写上 display:block? 二. ...
- HDU 2236 无题Ⅱ
HDU 2236 无题Ⅱ 题目大意 这是一个简单的游戏,在一个\(n*n\)的矩阵中,找n个数使得这n个数都在不同的行和列里并且要求这n个数中的最大值和最小值的差值最小. solution 暴枚\(i ...
- 「疫期集训day9」七月
我们暂时被击退了,可恶的德国佬----我们集结了英国人,意大利人,荷兰人,澳大利亚人,来自世界各地,我们万众一心,还能失守亚眠?----亚眠中坚守的协约国士兵 日常考爆,T1貌似全场就我傻乎乎的推式子 ...
- 神奇的建图方式(Tarjan)——小z玩游戏
原题来自与:洛谷 P5676(GZOI2017) 链接: https://www.luogu.com.cn/problem/P5676 题面: 题意比较明显,如果已经建好了边,那么跑个Tarjan ...
- CF819B Mister B and PR Shifts 题解
题目 Some time ago Mister B detected a strange signal from the space, which he started to study. After ...