FutureTask源码分析
Thread类的run方法返回值类型是void,因此我们无法直接通过Thread类获取线程执行结果。如果要获取线程执行结果就需要使用FutureTask。用法如下:
class CallableImpl implements Callable{@Overridepublic Object call() throws Exception {//do somethings//return result;}
}
FutureTask futureTask = new FutureTask(new CallableImpl());
new Thread(futureTask).start();
try {//获取线程执行结果Object result = futureTask.get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}
在实例化FutureTask时构造函数传入了实现Callable接口的实例。而在实例化Thread类时,构造函数传入FutureTask实例。因此,我们可以猜测线程在执行run方法时必定会调用call方法,并且保存call方法返回的结果。
总览

通过类图,可以看到FutureTask主要实现了Runnable和Future。实现Runnable的run方法作为线程的执行体。正因为实现了Runnable,我们才可以使用FutureTask来创建线程。Future接口定义了如下几个方法
public interface Future<V> {
/***取消线程执行的任务*/
boolean cancel(boolean mayInterruptIfRunning);
/***判断任务是否被取消*如果任务在正常完成前因调用cancel方法而被取消,返回true*/
boolean isCancelled();
/*** 判断任务是否完成,如果任务已经完成,返回true* 完成可能是由于正常终止、异常或取消——在这些情况下,此方法都将返回true。*/
boolean isDone();
/*** 获取任务执行的结果,调用该方法时,如果任务还没有执行完成,将会阻塞当前线程* 直到任务完成或者被中断*/
V get() throws InterruptedException, ExecutionException;
/*** 获取任务执行的结果,调用该方法时,如果任务还没有执行完成,将会阻塞当前线程* 直到任务完成或者被中断或者超时*/
V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
可以看出FutureTask具备获取线程任务执行结果、取消线程任务的能力。
成员变量
public class FutureTask<V> implements RunnableFuture<V> {//state标识任务的运行状态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;//任务的执行体,任务完成后,将会设置成nullprivate Callable<V> callable;//任务执行体的返回结果private Object outcome; //运行callable的线程private volatile Thread runner;//等待任务执行结果的线程队列private volatile WaitNode waiters;static final class WaitNode {//当前节点代表的线程volatile Thread thread;//下一个节点volatile WaitNode next;WaitNode() { thread = Thread.currentThread(); }}}
成员变量的含义只有在分析具体的方法代码和作者的注释时才能知晓。接下来具体分析FutureTask是如何实现保存任务执行结果和获取结果的。
源码分析
在分析FutureTask源码前,需要对其中使用到jdk的方法做个简单的介绍。其中Unsafe类提供的cas操作的相关方法。
public final native boolean compareAndSwapObject(Object obj,
long offset, Object expect, Object update);
- obj :要修改字段的对象;
- offset :要修改的字段在对象内的偏移量;
- expect : 字段的期望值;
- update :如果该字段的值等于字段的期望值,用于更新字段的新值;
LockSupport的park和unpark提供了阻塞和解除阻塞线程的有效方法,park会使当前线程阻塞,unpark可以唤醒指定的线程。
public static void park() {UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {if (thread != null)UNSAFE.unpark(thread);
}
构造函数
public FutureTask(Callable<V> callable) {if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW;
}
接收callable实例并赋值给成员变量callable,将任务状态初始化为NEW。
run方法
public void run() {//先检查任务的状态,如果任务状态是NEW。利用cas操作设置runner为当前执行任务的线程//这里是为了确保在多线程的情况下任务执行和结果设置的安全性及一致性//比如下面的代码,会导致一个任务在多个线程中运行。// FutureTask futureTask = new FutureTask(task);// futureTask.run();// Thread thread = new Thread(futureTask);// Thread thread1 = new Thread(futureTask);// thread.start();// thread1.start();if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {//调用callable方法,执行真正的任务逻辑result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;//执行异常处理,将任务状态修改为EXCEPTIONALsetException(ex);}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 interruptsint s = state;//handlePossibleCancellationInterrupt需要结合cancel方法分析if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}
}protected void set(V v) {//在outcome还没有保存返回结果前,先将任务状态设置为COMPLETING(正在完成)if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//保存任务运行结果outcome = v;//将任务状态设置为正常完成UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state//结束任务:唤醒所有因调用get方法而阻塞的线程,并清空等待队列finishCompletion();}
}protected void setException(Throwable t) {//在outcome还没有保存返回结果前,先将任务状态设置为COMPLETING(正在完成)if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//将任务的结果设置为异常信息outcome = t;//将任务状态设置为EXCEPTIONAL(异常中断)UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); //结束任务:唤醒所有因调用get方法而阻塞的线程,并清空等待队列finishCompletion();}
}/***唤醒所有因调用get方法而阻塞的线程,并清空等待队列*/
private void finishCompletion() {// assert state > COMPLETING;for (WaitNode q; (q = waiters) != null;) {//先将成员变量waiters设置为nullif (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {//从头开始遍历等待队列,唤醒其每个节点对应的线程for (;;) {Thread t = q.thread;if (t != null) {q.thread = null;LockSupport.unpark(t);}//获取下一个节点WaitNode next = q.next;if (next == null)break;//当前节点next指向null,当前节点从等待队列中断开,之后被GC回收q.next = null; // unlink to help gcq = next;}break;}}done();callable = null; // to reduce footprint
}
从run方法中,FutureTask的生命周期和线程的生命周期有一定的关联:
当FutureTask的state为NEW时,执行任务的线程可能处于New状态、Runable状态(线程在操作系统中被创建,处于等待CPU时间或运行中)、Blocked状态(线程在等待锁)。
当程序调用call方法后,在将call的执行结果保存到FutureTask的成员变量outcome前,会将FutureTask设置为COMPLETING。此时FutureTask的COMPLETING 对应线程的Runable状态。
如果程序调用call发生异常,FutureTask最终被设置为EXCEPTIONAL,正常执行则被设置为NORMAL,此时线程即将进入Terminated状态。
get方法
/***无限时长的等待获取执行结果*/
public V get() throws InterruptedException, ExecutionException {int s = state;//COMPLETING代表任务正在保存执行结果。<=这个状态,说明任务执行还没有保存执行结果//则会调用awaitDone方法等待执行结果。if (s <= COMPLETING)s = awaitDone(false, 0L);return report(s);
}
/***有限时长的等待获取执行结果*/
public V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException {if (unit == null)throw new NullPointerException();int s = state;if (s <= COMPLETING &&(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)throw new TimeoutException();return report(s);
}/***等待任务完成,在被中断或超时时终止*/
private int awaitDone(boolean timed, long nanos)throws InterruptedException {final long deadline = timed ? System.nanoTime() + nanos : 0L;WaitNode q = null;//当前节点是否在等待队列中boolean queued = false;for (;;) {//检查当前获取结果的线程是否被中断,如果被中断,从等待队列中移除,并抛出中断异常if (Thread.interrupted()) {removeWaiter(q);throw new InterruptedException();}int s = state;if (s > COMPLETING) {if (q != null)q.thread = null;return s;}else if (s == COMPLETING) // cannot time out yetThread.yield();else if (q == null)//1、创建一个新的节点q = new WaitNode();else if (!queued)//如果新的节点没有在排队,将这个节点加入到队列的头部queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);else if (timed) {//判断是否超时nanos = deadline - System.nanoTime();if (nanos <= 0L) {removeWaiter(q);return state;}//阻塞当前线程LockSupport.parkNanos(this, nanos);}else阻塞当前线程LockSupport.park(this);}
}
/**
*遍历等待队列移除节点
*/
private void removeWaiter(WaitNode node) {if (node != null) {//首先将节点thread设置为null,防止节点被意外地再次使用或唤醒//同时thread =null的节点是作为需要被移除节点的标记node.thread = null;retry:for (;;) { // restart on removeWaiter race//声明pre q s 三个WaitNode变量for (WaitNode pred = null, q = waiters, s; q != null; q = s) {s = q.next;//如果节点的thread !=null说明当前节点不需要被移除,遍历下一个if (q.thread != null)pred = q;else if (pred != null) {//上一个节点pred != null,说明当前节点不是队列的第一个节点//则将pred.next指向当前节点的下一个节点s,即跳过了当前节点pred.next = s;if (pred.thread == null) // check for race//队列在遍历过程中发生了变化,从头开始遍历continue retry;}//如果当前节点是头节点,将头节点设置为当前节点的下一个节点else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,q, s))//队列在遍历过程中发生了变化,从头开始遍历continue retry;}//完成对等待队列的遍历,成功移除了节点(无论是通过更新队列头部还是通过跳过内部节点)//退出break;}}
}
cancel方法
public boolean cancel(boolean mayInterruptIfRunning) {if (!(state == NEW &&UNSAFE.compareAndSwapInt(this, stateOffset, NEW,mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))return false;try { // in case call to interrupt throws exceptionif (mayInterruptIfRunning) {try {Thread t = runner;if (t != null)t.interrupt();} finally { // final stateUNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);}}} finally {finishCompletion();}return true;}
cancel返回值是boolean类型,任务取消成功,返回true,任务取消失败返回false;参数mayInterruptIfRunning表示是否尝试取消正在执行中的任务。
1、如果任务状态不是NEW状态,直接返回false,通过对run方法的分析,可以知道当FutureTask状态不为NEW时,任务已经被取消或者已经执行了call方法,无法取消任务。
2、任务状态是NEW
2.1 参数mayInterruptIfRunning为false,设置任务状态为CANCELLED,从run方法中可以得到,正在执行的任务不会被取消,还未开始的任务会被取消。
2.2 参数mayInterruptIfRunning为true,尝试调用正在执行任务的线程的interrupt()方法(在一个线程内部存在着名为interrupt flag的标识,如果一个线程被interrupt,那么它的flag将被设置,但是如果当前线程正在执行可中断方法被阻塞时,如Object的wait方法,Thread的sleep、join方法等,调用interrupt方法将其中断,反而会导致flag被清除)
再回过头看看run方法最后的handlePossibleCancellationInterrupt
public void run() {......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 interruptsint s = state;//handlePossibleCancellationInterrupt需要结合cancel方法分析if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}
}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)Thread.yield(); // wait out pending interrupt// assert state == INTERRUPTED;// We want to clear any interrupt we may have received from// cancel(true). However, it is permissible to use interrupts// as an independent mechanism for a task to communicate with// its caller, and there is no way to clear only the// cancellation interrupt.//// Thread.interrupted();}
这个方法到底有什么作用呢,作者通过源代码注释告诉我们这里的自旋目的是如果其他线程调用了cancel(true)方法,确保中断只能传递给当前执行任务的线程runner,并且state在runner线程执行期间最终能够被设置为INTERRUPTED。当线程调用cancel(true)方法方法时,先将任务状态设置为INTERRUPTING,再执行运行任务线程的中断方法,最后将任务状态设置为INTERRUPTED。执行任务的线程检测到有其他线程正在中断任务时,会等待完成中断操作后再退出。
通过对源码的阅读,我们大致了解到了:任务是如何执行并且保存执行结果,完成任务后,如何唤醒等待获取执行结果的线程。在获取执行结果时,如果任务还未完成,如何进入等待队列,如果等待超时或者被中断,如何从等待队列中移除。
相关文章:
FutureTask源码分析
Thread类的run方法返回值类型是void,因此我们无法直接通过Thread类获取线程执行结果。如果要获取线程执行结果就需要使用FutureTask。用法如下: class CallableImpl implements Callable{Overridepublic Object call() throws Exception {//do somethin…...
Highcharts甘特图基本用法(highcharts-gantt.js)
参考官方文档: https://www.highcharts.com/docs/gantt/getting-started-gantt https://www.highcharts.com/demo/gantt/project-management https://www.hcharts.cn/demo/gantt 链接在下面按需引入 https://code.highcharts.com/gantt/highcharts-gantt.js htt…...
【Linux庖丁解牛】—Linux基本指令(上)!
🌈个人主页:秋风起,再归来~🔥系列专栏: Linux庖丁解牛 🔖克心守己,律己则安 目录 1、 pwd命令 2、ls 指令 3、cd 指令 4、Linux下的根目录 5、touch指令 6、 stat指令 7、mkdi…...
node.js 中的进程和线程工作原理
本文所有的代码均基于 node.js 14 LTS 版本分析 概念 进程是对正在运行中的程序的一个抽象,是系统进行资源分配和调度的基本单位,操作系统的其他所有内容都是围绕着进程展开的 线程是操作系统能够进行运算调度的最小单位,其是进程中的一个执…...
Qt/C++ TCP调试助手V1.1 新增图像传输与接收功能(附发布版下载链接)
发布版本链接 通过百度网盘分享的文件:TCP调试助手V1.zip(含客户端与服务器) 链接:https://pan.baidu.com/s/14LTRPChPhYdwp_s6KeyBiA?pwdcedu 提取码:cedu 基于Qt/C实现了一款功能丰富的TCP服务器与客户端调试助手…...
DNS解析流程
DNS解析流程: 浏览器DNS缓存: 当我们在浏览器中访问某个域名时,浏览器首先会检查自己内部的DNS缓存,看是否有该域名的对应IP地址。如果有,直接使用缓存中的IP地址,跳过后续步骤。 本地系统DNS缓存…...
[PTA]7-1 藏头诗
[PTA]7-1 藏头诗 本题要求编写一个解密藏头诗的程序。 注:在 2022 年 7 月 14 日 16 点 50 分以后,该题数据修改为 UTF-8 编码。 输入格式: 输入为一首中文藏头诗,一共四句,每句一行。注意:一个汉字占三…...
每日OJ题_牛客_WY22 Fibonacci数列(斐波那契)
目录 牛客_WY22 Fibonacci数列(斐波那契) 解析代码 牛客_WY22 Fibonacci数列(斐波那契) Fibonacci数列_牛客题霸_牛客网 解析代码 求斐波那契数列的过程中,判断⼀下:何时 n 会在两个 fib 数之间。 #in…...
SQL 查询语句汇总
在软件开发和数据分析中,SQL(结构化查询语言)是与数据库交互的重要工具。为了更好地理解 SQL 查询语句的使用,本文将设计一个简单的数据库,包括几张表,并通过这些表展示各种 SQL 查询的应用。 一、背景信息…...
封装一个语言识别文字的方法
语音识别 需求: 参考官方文档,整合语音识别apicallback 的写法改为 Promise 的版本 在startRecord中: 参考文档实例化-开启转换将录制的内容传递给录音识别回调函数中的 Log,改为 Logger 在closeRecord: 结束识别…...
解决 iOS App Tracking Transparency 权限问题
解决 iOS App Tracking Transparency 权限问题 在 iOS 14 及更高版本中,Apple 引入了 App Tracking Transparency (ATT) 框架,要求应用在跟踪用户之前必须获得用户的明确许可。这通常涉及到访问用户的广告标识符(IDFA)。如果没有…...
ClickHouse 的底层架构和原理
ClickHouse 是一个用于实时分析和处理大规模数据的列式数据库,其设计目标是高效地处理海量数据的查询需求。它特别适合 OLAP(Online Analytical Processing)场景,能够在不依赖复杂的索引结构的情况下,实现极快的查询速…...
rtmp推流
获取摄像头名称 打开命令行工具,运行以下命令以列出所有可用的视频设备: ffmpeg -f dshow -list_devices true -i dummy查找输出中的“Video devices”部分,记录下你的摄像头名称。 构建推流命令 ffmpeg -f dshow -i video"摄像头名称…...
【数据库】死锁排查方式
定位 查是否锁表 select username,lockwait,status,machine,program from v$session where sid in (select session_id from v$locked_object); 查锁表sql select sql_text from v$sql where hash_value in (select sql_hash_value from v$session where sid in (select s…...
去耦合的一些建议
尽量少用全局变量,以减少状态共享和潜在的副作用。 模块化设计:将代码分成小模块,每个模块独立实现特定功能,减少模块之间的相互依赖。 封装:将数据和操作封装在类中,控制对内部状态的访问,避…...
SpringBoot+Thymeleaf图书管理系统
一、项目介绍 > 这是一个基于SpringBootThymeleaf实现的图书管理系统。 > 包含图书管理、作者管理、分类管理、出版社管理等功能。 > 界面简洁美观,代码结构清晰,完成度比较高,适用于JAVA初学者作为参考项目。 二、项目演示 三…...
TDengine 签约前晨汽车,解锁智能出行的无限潜力
在全球汽车产业转型升级的背景下,智能网联和新能源技术正迅速成为商用车行业的重要发展方向。随着市场对环保和智能化需求的日益增强,企业必须在技术创新和数据管理上不断突破,以满足客户对高效、安全和智能出行的期待。在这一背景下…...
模板字符串中定义方法并传参
遇到一个使用js es6的模板字符串进行事件绑定和传参的问题,这个问题的引起是因为使用innerHTML插入了一大串html并进行事件的绑定和传参。 以react为例,写一个demo记录一下 模板字符串中写方法的话需要用onclick来定义,传参需要这么写${char…...
Numpy 数组元素添加与元素删除函数详解
元素添加 Numpy中有类似python列表操作函数append()及insert(),但是用法稍有不同,append()及insert()不作为数组的实例方法使用。 np.append() np.append()的参数如下 def append(arr, values, axisNone): 其中,arr为数组对象࿰…...
【Python】高效图像处理库:pyvips
月亮慢慢变圆,日子慢慢变甜。 在图像处理领域,pyvips 是一个轻量级且高效的库,适合处理大规模图像、实现高性能的操作。相较于其他常见的图像处理库如 PIL 或 OpenCV,pyvips 以其低内存占用和出色的速度脱颖而出。本文将介绍 pyv…...
身份证OCR识别接口接入实战:Python/Java/PHP/C#四语言代码示例与踩坑指南
#身份证OCR, #OCR接口, #API接入, #Python示例, #Java示例, #PHP示例, #踩坑指南, #石榴智能, #实名认证, #图片识别 身份证OCR识别接口接入实战:Python/Java/PHP/C#四语言代码示例与踩坑指南 作者:石榴智能技术团队 一、前言 身份证OCR识别已经不是什…...
ThinkPad开机嘀嘀响或报2100/2110错误?可能是硬盘松了!自己动手检测与修复指南
ThinkPad开机嘀嘀响或报2100/2110错误?三步排查硬盘接触不良问题ThinkPad用户对那个标志性的开机"嘀嘀"声再熟悉不过——正常情况下它意味着系统自检通过。但当这个声音变成急促的报警音,伴随屏幕上出现"2100 Detection error"或&qu…...
别再乱用npm install了!手把手教你用npx only-allow为项目指定包管理器(支持pnpm/yarn/npm)
用only-allow统一团队包管理器:从配置到CI的全流程指南 你是否曾经在拉取一个新项目后,面对npm install、yarn还是pnpm i的抉择感到困惑?或者更糟的是,团队成员混用不同包管理器导致node_modules结构不一致,引发各种诡…...
3分钟掌握HashCalculator:你的文件完整性守护专家
3分钟掌握HashCalculator:你的文件完整性守护专家 【免费下载链接】HashCalculator 哈希值计算工具,批量计算/批量校验/查找重复文件/改变哈希值等,支持集成到系统右键菜单 项目地址: https://gitcode.com/gh_mirrors/ha/HashCalculator …...
【python】ImportError: DLL load failed while importing QtWidgets: 找不到指定的程序。重新安装后搞定
文章目录前言一、PyQt6引用后报错二、使用步骤总结前言 想做个好看的界面,引用了PyQt6,却产生了新问题。 pip install pyqt6-tools,优先做这个动作进行修复。 一、PyQt6引用后报错 python里引用: from PyQt6.QtWidgets import…...
【Veo 2提示词SOP白皮书】:从模糊意图到像素级输出的8步标准化工作流(附NASA级测试用例库)
更多请点击: https://intelliparadigm.com 第一章:Veo 2提示词工程的本质与范式跃迁 Veo 2并非单纯升级的视频生成模型,而是一次提示词工程范式的根本性重构——它将传统“指令式提示”(prompt-as-command)转向“意图…...
通用物联网开发板设计:基于ESP8266的硬件集成与开发实践
1. 项目概述:为什么我们需要一块“通用”的物联网开发板?在捣鼓了几年物联网项目之后,我发现自己桌面上堆满了各种开发板:ESP8266、ESP32、Arduino Uno、STM32 Nucleo……每个项目都要重新连线、配置电源、焊接传感器接口…...
Arduino ADC自检:用RC电路诊断模数转换器故障
1. 项目概述:当你的体重秤开始“说谎”你有没有遇到过这样的情况:站上家里的电子体重秤,屏幕上跳出来的数字让你瞬间怀疑人生?要么是轻得离谱,要么是重得吓人,更诡异的是,它可能只在两个固定的、…...
TorchEasyRec:阿里巴巴开源的推荐系统深度学习框架详解
第一部分:项目概览与核心功能 一、项目简介:什么是 TorchEasyRec? TorchEasyRec 是阿里巴巴 PAI 团队开发的基于 PyTorch 的推荐系统框架,专门用于构建生产级别的深度学习推荐模型。简单来说,它就是一个让你能够快速…...
AVR+ESP8266双核架构打造独立WiFi天气显示器:从硬件设计到软件实现
1. 项目概述:一个独立WiFi天气显示器的诞生几年前,我琢磨着在书桌上放一个能实时显示天气信息的小玩意儿,市面上成品要么功能单一,要么价格不菲,要么数据源依赖复杂的服务器。于是,我决定自己动手ÿ…...
