并发系列(二)——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请求, ...
随机推荐
- shell基本正则表达式
基本正则表达式 星号* 匹配它前面的字符串或正则表达式任意次(包括0次).比如,“1122*” 将匹配11+1个或多个2,其可能匹配的字符串将是112.1122.112222.11223343等 句点 ...
- 洛谷 P2296 【寻找道路】
这道题真的很女少啊 言归正传: 这道题其实就是考验的思路,读题后,我们发现对于某个点他所连接的点必须连接终点,那么我们直接反向存图,从终点进行bfs,可以找到未连接的点,然后对这些点所连接的点进行标记 ...
- python之shutil模块的使用
shutil模块 shutil模块是一种高级的文件操作工具,其对文件的复制与删除操作非常强大,shutil 名字来源于 shell utilities,该模块拥有许多文件(夹)操作的功能,包括复制.移 ...
- 【python爬虫实战】使用Selenium webdriver采集山东招考数据
目录 1.目标 2.Selenium webdriver说明 2.1 为什么使用webdriver 2.2 webdriver支持浏览器 2.3 配置与使用说明 3.采集 3.1 分析网站 3.2 遍 ...
- python基础知识-1
1.python是静态的还是动态的?是强类型还弱类型? python是强类型的动态脚本语言: 强类型:不允许不同类型相加 动态:不使用显示类型声明,且确定一个变量的类型是在第一次给它赋值的时候 脚本语 ...
- 这些 CSS 命名规范将省下你大把调试时间
我听说很多开发者厌恶 CSS.而在我的经验中,这往往是由于他们并没有花时间来学习 CSS. CSS 算不上是最优美的『语言』,但迄今二十多年来,它都是美化 web 举足轻重的工具.从这点来说,也还算不 ...
- 利用IDEA搭建JDK源码阅读环境
利用IDEA搭建JDK源码阅读环境 首先新建一个java基础项目 基础目录 source 源码 test 测试源码和入口 准备JDK源码 下图框起来的路径就是jdk的储存位置 打开jdk目录,找到sr ...
- (三)ansible playbook
一,YAML语法 YAML的语法和其他高阶语言类似并且可以简单表达清单.散列表.标量等数据结构.(列表用横杆表示,键值对用冒号分割,键值对里又可以嵌套另外的键值对) YAML文件扩展名通常为.yaml ...
- URL编码与二次encodeURI
转自:http://foryougeljh.iteye.com/blog/1456706 一般来说,URL只能使用英文字母.阿拉伯数字和某些标点符号,不能使用其他文字和符号.比如,世界上有英文字母的网 ...
- wcf服务各种情况下应用
1.控制台调用 第一步,添加wcf服务 2.写接口,记得要加好契约特性. 3.声明一个类继承wcf服务. 4.ipconfig配置 5.控制台运行 6.运行app.config里面,加上调用的接口方法 ...