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…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...