背景

  本文基于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. 2020/6/11 JavaScript高级程序设计 DOM

    DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序接口).他描绘了一个层次化的节点树,允许开发人员添加.移除和修改页面的某一部分. 10.1 节点层次 DOM将任何HTML和XML ...

  2. 平时自己常用的git指令

    增删改查 创建标签 $ git tag -a v1.4 -m 'my version 1.4' 用 -a (译注:取 annotated 的首字母)指定标签名字即可 -m 选项则指定了对应的标签说明 ...

  3. node+ajax实战案例(1)

    1.mysql入门 1.1.数据库相关概念 1.1.1.什么是数据? 描述事物的符号记录称为数据,描述事物的符号可以是数字.文字.声音.图片.视频等,有多种表现形式,都可以经过数字化后存入计算机 1. ...

  4. int与bigdecimal的相互转换

    int转bigdecimal BigDecimal number = new BigDecimal(0); int value=score; number=BigDecimal.valueOf((in ...

  5. 学习 Spring Boot 知识看这一篇就够了

    从2016年因为工作原因开始研究 Spring Boot ,先后写了很多关于 Spring Boot 的文章,发表在技术社区.我的博客和我的公号内.粗略的统计了一下总共的文章加起来大概有六十多篇了,其 ...

  6. Scala数据结构(二)

    一.集合的基础操作 1,head头信息 //获取集合的第一个元素 val list = List(,,) list.head // 2,tail尾信息 //获取集合除去头元素之外的所有元素 val l ...

  7. 免费馅饼——移动dp

    免费馅饼 题目描述 SERKOI最新推出了一种叫做"免费馅饼"的游戏: 游戏在一个舞台上进行.舞台的宽度为 \(W\) 格,天幕的高度为 \(H\) 格,游戏者占一格. 开始时游戏 ...

  8. PE文件格式详解(二)

    0x00 前言 上一篇讲到了PE文件头的中IMAGE_FILE_HEADER结构的第二个结构,今天从IMAGE_FILE_HEADER中第三个结构sizeOfOptionalHeader讲起.这个字段 ...

  9. rabbitmq部署及配置与验证

    1. 场景描述 朋友项目需要弄个测试环境,稍微帮忙了下,系统不复杂,但是需要自己安装mysql.Reids.Es.RabbitMq等,Mq主要用在同步用户信息与发送站内消息和短信上,RabbitMq以 ...

  10. jira仪表盘的建立与共享

    一般在项目测试阶段,可以通过jira仪表盘清晰的展示bug的各个状态的数量,各个开发人员的bug数量. 有效督促开发解决问题. 也为测试日报提供了良好的数据支持,减少人工统计的工作量. 1.建议筛选器 ...