背景

  本文基于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类源码简析的更多相关文章

  1. Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析

    一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...

  2. django-jwt token校验源码简析

    一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...

  3. SpringMVC学习(一)——概念、流程图、源码简析

    学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...

  4. AFNetworking源码简析

    AFNetworking基本是苹果开发中网络请求库的标配,它是一个轻量级的网络库,专门针对iOS和OS X的网络应用设计,具有模块化的架构和丰富的APIs接口,功能强大并且使用简单,深受苹果应用开发人 ...

  5. 0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

    1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日 ...

  6. DRF之APIView源码简析

    一. 安装djangorestframework 安装的方式有以下三种,注意,模块就叫djangorestframework. 方式一:pip3 install djangorestframework ...

  7. 源码简析XXL-JOB的注册和执行过程

    一,前言 XXL-JOB是一个优秀的国产开源分布式任务调度平台,他有着自己的一套调度注册中心,提供了丰富的调度和阻塞策略等,这些都是可视化的操作,使用起来十分方便. 由于是国产的,所以上手还是比较快的 ...

  8. 源码简析Spring-Integration执行过程

    一,前言 Spring-Integration基于Spring,在应用程序中启用了轻量级消息传递,并支持通过声明式适配器与外部系统集成.这一段官网的介绍,概况了整个Integration的用途.个人感 ...

  9. OpenStack之Glance源码简析

    Glance简介 OpenStack镜像服务器是一套虚拟机镜像发现.注册.检索. glance架构图: Glance源码结构: glance/api:主要负责接收响应镜像管理命令的Restful请求, ...

随机推荐

  1. shell基本正则表达式

    基本正则表达式 星号* 匹配它前面的字符串或正则表达式任意次(包括0次).比如,“1122*” 将匹配11+1个或多个2,其可能匹配的字符串将是112.1122.112222.11223343等 句点 ...

  2. 洛谷 P2296 【寻找道路】

    这道题真的很女少啊 言归正传: 这道题其实就是考验的思路,读题后,我们发现对于某个点他所连接的点必须连接终点,那么我们直接反向存图,从终点进行bfs,可以找到未连接的点,然后对这些点所连接的点进行标记 ...

  3. python之shutil模块的使用

    shutil模块 shutil模块是一种高级的文件操作工具,其对文件的复制与删除操作非常强大,shutil 名字来源于 shell utilities,该模块拥有许多文件(夹)操作的功能,包括复制.移 ...

  4. 【python爬虫实战】使用Selenium webdriver采集山东招考数据

    目录 1.目标 2.Selenium webdriver说明 2.1 为什么使用webdriver 2.2 webdriver支持浏览器 2.3 配置与使用说明 3.采集 3.1 分析网站 3.2 遍 ...

  5. python基础知识-1

    1.python是静态的还是动态的?是强类型还弱类型? python是强类型的动态脚本语言: 强类型:不允许不同类型相加 动态:不使用显示类型声明,且确定一个变量的类型是在第一次给它赋值的时候 脚本语 ...

  6. 这些 CSS 命名规范将省下你大把调试时间

    我听说很多开发者厌恶 CSS.而在我的经验中,这往往是由于他们并没有花时间来学习 CSS. CSS 算不上是最优美的『语言』,但迄今二十多年来,它都是美化 web 举足轻重的工具.从这点来说,也还算不 ...

  7. 利用IDEA搭建JDK源码阅读环境

    利用IDEA搭建JDK源码阅读环境 首先新建一个java基础项目 基础目录 source 源码 test 测试源码和入口 准备JDK源码 下图框起来的路径就是jdk的储存位置 打开jdk目录,找到sr ...

  8. (三)ansible playbook

    一,YAML语法 YAML的语法和其他高阶语言类似并且可以简单表达清单.散列表.标量等数据结构.(列表用横杆表示,键值对用冒号分割,键值对里又可以嵌套另外的键值对) YAML文件扩展名通常为.yaml ...

  9. URL编码与二次encodeURI

    转自:http://foryougeljh.iteye.com/blog/1456706 一般来说,URL只能使用英文字母.阿拉伯数字和某些标点符号,不能使用其他文字和符号.比如,世界上有英文字母的网 ...

  10. wcf服务各种情况下应用

    1.控制台调用 第一步,添加wcf服务 2.写接口,记得要加好契约特性. 3.声明一个类继承wcf服务. 4.ipconfig配置 5.控制台运行 6.运行app.config里面,加上调用的接口方法 ...