并发系列(二)——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请求, ...
随机推荐
- C#数据结构与算法系列(十九):选择排序算法(SelectSort)
1.介绍 选择排序算法属于内部排序算法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置达到排序的目的 时间复杂度:O(n^2) 双层for 2.思想 选择排序(select sorti ...
- python在循环中追加字典
1. 在循环中用append增加key值,最终生成全是key值的list: 2. 在循环中用append增加value值,最终生成全是value值的list: 3. 最后用zip将两个list合并成一 ...
- 全栈的自我修养: 001环境搭建 (使用Vue,Spring Boot,Flask,Django 完成Vue前后端分离开发)
全栈的自我修养: 环境搭建 Not all those who wander are lost. 彷徨者并非都迷失方向. Table of Contents @ 目录 前言 环境准备 nodejs v ...
- ASP.NET MVC 中解决Session,Cookie等依赖的方式
原文:https://blog.csdn.net/mzl87/article/details/90580869 本文将分别介绍在MVC中使用Filter和Model Binding两种方式来说明如何解 ...
- 每日一题 - 剑指 Offer 32 - I. 从上到下打印二叉树
题目信息 时间: 2019-06-25 题目链接:Leetcode tag:BFS(广度优先搜索) 队列 难易程度:中等 题目描述: 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印 ...
- 【git】配置git命令行别名
引言:由于有些git命令较长,记起来比较麻烦,为了git工具使用的方便,为命令行取别名有很大的必要. 1.在家目录添加.gitconfig文件. 此文件在创建git仓库时,一般是没有的,需要手动添加. ...
- CentOS7下安装和配置SVN
1. 由于是在CentOS7最小化安装的操作系统环境安装SVN,我们首先排除一些环境因素.在此首先关闭了防火墙,安装了vim文本编辑工具. 2. 使用yum install -y subversi ...
- Java编程技术之浅析Java容器技术
Java容器 集合是一种存储数据的容器,是Java开发中使用最频繁的对象类型之一. 或许提起Collection,都会第一时间意识到List和Set以及Map等相关关键词.因为这几乎是我们日常开发里接 ...
- 【Python篇】工厂模式
工厂方法模式 前言 在<设计模式>一书中工厂模式提到了: 工厂方法模式(Factory Method) 抽象工厂模式 (Abstract Factory) 但是在实际过程中还有一种工厂模式 ...
- 使用Python进行XML解析
XML 指可扩展标记语言(eXtensible Markup Language),常被设计用来传输和存储数据. 在进行医学图像标注时,我们常使用XML格式文件来存储标注,以下展示了使用Python来提 ...