深入解析FutureTask:原理与实战
我们来深入解析 FutureTask。下面将从它们的用法开始,逐步深入到底层实现、方法和接口设计,并探讨它们对于我们自己设计实现以及其他编程实践的学习意义。
主要就是放入等待队列(CAS操作一个链表头),完成任务的线程唤醒等待线程继续逻辑。
FutureTask 深入解析
FutureTask 是一个可取消的异步计算任务。它实现了 RunnableFuture 接口,该接口同时继承了 Runnable 和 Future 接口。这意味着 FutureTask 既可以作为一个 Runnable 被执行器(如 ExecutorService)执行,也可以作为一个 Future 来获取异步计算的结果、查询计算状态或取消计算。
FutureTask 通常用于包装一个 Callable 或 Runnable 对象。
基本用法示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;public class FutureTaskExample {public static void main(String[] args) throws Exception {// 1. 创建 Callable 任务Callable<String> callableTask = () -> {System.out.println(Thread.currentThread().getName() + " is executing callable task...");TimeUnit.SECONDS.sleep(2); // 模拟耗时操作return "Callable Result";};// 2. 使用 Callable 创建 FutureTaskFutureTask<String> futureTask1 = new FutureTask<>(callableTask);// 也可以使用 Runnable 创建 FutureTask (通常需要提供一个结果,若无则为 null)Runnable runnableTask = () -> {System.out.println(Thread.currentThread().getName() + " is executing runnable task...");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {Thread.currentThread().interrupt();}};FutureTask<String> futureTask2 = new FutureTask<>(runnableTask, "Runnable Result (if provided)");// 3. 提交 FutureTask 到 ExecutorService 执行// FutureTask 本身就是 Runnable,可以直接被线程执行或提交给 ExecutorServiceExecutorService executor = Executors.newFixedThreadPool(2);executor.submit(futureTask1);executor.submit(futureTask2);// 或者直接 new Thread(futureTask1).start();System.out.println("Tasks submitted.");// 4. 获取结果 (get() 方法会阻塞直到任务完成)try {System.out.println("Waiting for futureTask1 result...");String result1 = futureTask1.get(); // 阻塞等待System.out.println("futureTask1 result: " + result1);System.out.println("Waiting for futureTask2 result with timeout...");// get(long timeout, TimeUnit unit) 可以设置超时String result2 = futureTask2.get(2, TimeUnit.SECONDS);System.out.println("futureTask2 result: " + result2);} catch (InterruptedException e) {System.err.println("Task interrupted: " + e.getMessage());} catch (java.util.concurrent.ExecutionException e) {System.err.println("Task execution failed: " + e.getCause());} catch (java.util.concurrent.TimeoutException e) {System.err.println("Task timed out: " + e.getMessage());}// 5. 检查任务状态和取消if (!futureTask1.isDone()) {System.out.println("futureTask1 is not done yet.");}if (futureTask1.isCancelled()) {System.out.println("futureTask1 was cancelled.");}// 尝试取消一个未完成的任务FutureTask<Integer> cancellableTask = new FutureTask<>(() -> {TimeUnit.SECONDS.sleep(5);return 100;});new Thread(cancellableTask).start();Thread.sleep(100); // 给任务一点时间启动boolean cancelled = cancellableTask.cancel(true); // true 表示如果任务正在运行,则中断它System.out.println("CancellableTask cancelled: " + cancelled);System.out.println("CancellableTask isCancelled: " + cancellableTask.isCancelled());System.out.println("CancellableTask isDone: " + cancellableTask.isDone()); // cancel 后 isDone() 也为 trueexecutor.shutdown();}
}
FutureTask
是 Java 并发包中一个非常核心的类,它代表一个可取消的异步计算。它巧妙地结合了 Future
接口(用于获取异步计算的结果)和 Runnable
接口(使得它可以被 Executor
执行)。
1. 状态管理 (State Management)
FutureTask
内部维护一个 volatile int state
字段来表示任务的当前状态。状态包括:
-
NEW
: 初始状态,任务尚未开始或正在运行。 -
COMPLETING
: 任务已完成,正在设置结果(一个短暂的中间状态)。 -
NORMAL
: 任务正常完成,结果已设置。 -
EXCEPTIONAL
: 任务因抛出异常而完成,异常已设置。 -
CANCELLED
: 任务被取消(在开始运行前)。 -
INTERRUPTING
: 任务被取消,并且正在尝试中断运行任务的线程(一个短暂的中间状态)。 -
INTERRUPTED
: 任务被取消,并且运行任务的线程已被中断。
状态之间的转换通过 CAS (Compare-And-Set) 操作(使用 VarHandle STATE
)来保证原子性。
2. 任务执行 (run()
方法)
当 FutureTask
的 run()
方法被调用时(通常由一个 Executor
的工作线程调用):
-
首先会通过 CAS 操作尝试将
runner
字段(volatile Thread runner
)从null
设置为当前线程。这确保了只有一个线程可以实际执行任务。 -
如果设置成功并且任务状态是
NEW
,则会调用内部的Callable
对象的call()
方法。 -
如果
call()
方法正常返回,则调用set(V result)
方法设置结果,并将状态转换为NORMAL
。 -
如果
call()
方法抛出异常,则调用setException(Throwable t)
方法设置异常,并将状态转换为EXCEPTIONAL
。 -
在
finally
块中,runner
字段会被重置为null
。还会检查任务是否在运行期间被取消并需要中断(状态为INTERRUPTING
或INTERRUPTED
),如果是,则会调用handlePossibleCancellationInterrupt()
处理。
3. 获取结果 (get()
和 get(long, TimeUnit)
方法)
-
get()
方法:-
首先检查当前状态
s = state
。 -
如果任务尚未完成 (
s <= COMPLETING
),则调用awaitDone(boolean timed, long nanos)
方法阻塞等待。 -
一旦任务完成(状态变为
NORMAL
,EXCEPTIONAL
,CANCELLED
, 或INTERRUPTED
),awaitDone
返回,然后get()
方法调用report(int s)
来返回结果或抛出相应的异常。 -
NORMAL
: 返回结果。 -
EXCEPTIONAL
: 抛出ExecutionException
(包装了原始异常)。 -
CANCELLED
或INTERRUPTED
: 抛出CancellationException
。
-
-
get(long, TimeUnit)
** 方法**:类似get()
,但带有超时机制。如果在超时时间内任务未完成,则抛出TimeoutException
。
4. 取消任务 (cancel(boolean mayInterruptIfRunning)
方法)
-
尝试通过 CAS 将状态从
NEW
转换为CANCELLED
(如果mayInterruptIfRunning
为false
) 或INTERRUPTING
(如果mayInterruptIfRunning
为true
)。 -
如果 CAS 成功:
-
如果
mayInterruptIfRunning
为true
:-
获取
runner
线程。 -
如果
runner
不为null
,则调用runner.interrupt()
来中断执行任务的线程。 -
在
finally
块中,将状态设置为INTERRUPTED
(使用STATE.setRelease
保证内存可见性)。
-
-
最后,调用
finishCompletion()
来唤醒所有等待的线程。
-
-
如果 CAS 失败(例如任务已经完成或已被取消),则返回
false
。
5. 等待队列 (WaitNode
和 waiters
字段)
-
private volatile WaitNode waiters;
:这是一个指向等待线程链表头部的指针。这个链表是一个简单的 Treiber 栈 (LIFO 栈)。 -
WaitNode
是一个静态内部类,代码如下:
// ...
static final class WaitNode { volatile Thread thread; volatile WaitNode next; WaitNode() { thread = Thread.currentThread(); }
}
// ...
每个 WaitNode
封装了一个等待结果的线程 (thread = Thread.currentThread()
) 和一个指向下一个节点的指针 (next
)。
6. 阻塞和唤醒机制 (awaitDone()
和 finishCompletion()
)
-
awaitDone(boolean timed, long nanos)
:
当一个线程调用 get()
并且任务未完成时,会进入此方法。它会创建一个新的 WaitNode
,然后在一个循环中:
-
通过 CAS 将新的
WaitNode
添加到waiters
链表的头部(实现入栈)。 -
再次检查任务状态,如果已完成,则移除刚添加的节点并返回状态。
-
如果任务仍未完成,则调用
LockSupport.park(this)
(或LockSupport.parkNanos(this, nanos)
) 使当前线程阻塞。 -
当线程被唤醒时,如果是因为中断,则从等待队列中移除节点并抛出
InterruptedException
。如果是因为超时,则从等待队列中移除节点并返回当前状态。
-
finishCompletion()
:
当任务完成(通过 set
, setException
, 或 cancel
)时,此方法被调用。
-
它会遍历
waiters
链表,并对每个WaitNode
中的线程调用LockSupport.unpark(q.thread)
来唤醒它们。 -
遍历完成后,调用
done()
方法(这是一个空方法,供子类覆盖以执行完成回调)。 -
最后将
callable
设为null
以帮助 GC。
哪个线程负责管理唤醒 get
等待的线程?
负责唤醒等待线程的是完成任务的那个线程。具体来说:
-
如果是任务正常执行完成或抛出异常,那么是执行
run()
方法的线程(即runner
线程)在调用set()
或setException()
后,最终会调用finishCompletion()
来唤醒所有等待者。 -
如果是任务被取消,那么是调用
cancel()
方法的线程在成功取消任务后,会调用finishCompletion()
来唤醒所有等待者。
等待 get()
方法结果的线程被封装在 WaitNode
对象中。每个 WaitNode
包含:
-
volatile Thread thread;
: 对等待线程本身的引用。 -
volatile WaitNode next;
: 指向链表中下一个WaitNode
的引用。
这些 WaitNode
对象形成一个后进先出 (LIFO) 的栈式链表,其头节点由 FutureTask
的 volatile WaitNode waiters;
字段指向。当一个线程需要等待时,它会创建一个新的 WaitNode
并将其 CAS 到 waiters
链表的头部。当任务完成时,finishCompletion()
方法会遍历这个链表并唤醒每个节点中的线程。
设计优势
这种设计避免了使用更重的锁(如 AbstractQueuedSynchronizer
,早期版本的 FutureTask
使用过它),转而使用轻量级的 CAS 操作和 LockSupport
进行线程的阻塞和唤醒,这在很多情况下能提供更好的性能。
相关文章:
深入解析FutureTask:原理与实战
我们来深入解析 FutureTask。下面将从它们的用法开始,逐步深入到底层实现、方法和接口设计,并探讨它们对于我们自己设计实现以及其他编程实践的学习意义。 主要就是放入等待队列(CAS操作一个链表头),完成任务的线程唤…...

每天总结一个html标签——Audio音频标签
Audio标签 文章目录 Audio标签一、audio标签的定义与介绍1. 定义介绍2. 语法3. 支持的格式4.文本提示 二、audio标签的HTML属性1. autoplay2. loop3. muted4. preload 三、audio标签的常用DOM属性四、audio标签的常用事件四、默认样式五、自定义样式1. 示例2. 代码 六、播放 m3…...
使用 React Native 开发鸿蒙(HarmonyOS)运动健康类应用的系统化准备工作
⚙️ 一、环境与工具准备 双环境搭建 React Native 环境:安装 Node.js(≥18.x)、JDK(≥11)、Yarn。鸿蒙开发环境: 下载 DevEco Studio 4.0 及 HarmonyOS SDK;配…...

web3-Remix部署智能合约到“荷兰式”拍卖及以太坊gas费机制细讲
web3-Remix部署智能合约到“荷兰式”拍卖及以太坊gas费机制细讲 一、使用Remix演示智能合约部署 智能合约的代码编写一般都是在Remix上,Remix的好处的话就是可以在浏览器中快速开发和部署合约,无需在本地安装任何程序,十分适合新手。 对应…...

网络编程及原理(一)
目录 一 . 独立模式与网络互联 二 . 局域网 —— LAN (1)基于网线直连 (2)基于集线器组建 (3)基于交换机组建 (4)基于交换机和路由器组建 三 . 广域网 —— WAN 四 …...
superior哥AI系列第9期:高效训练与部署:从实验室到生产环境
🚀 superior哥AI系列第9期:高效训练与部署:从实验室到生产环境 嘿!小伙伴们!👋 欢迎来到superior哥AI系列第9期!经过前面8期的学习,你已经掌握了深度学习的核心技术。但是࿰…...

【Linux】进程 信号保存 信号处理 OS用户态/内核态
🌻个人主页:路飞雪吖~ 🌠专栏:Linux 目录 一、信号保存 ✨进程如何完成对信号的保存? ✨在内核中的表示 ✨sigset_t ✨信号操作函数 🪄sigprocmask --- 获取或设置当前进程的 block表 🪄s…...

[ Qt ] | 与系统相关的操作(一):鼠标相关事件
目录 信号和事件的关系 (leaveEvent和enterEvent) 实现通过事件获取鼠标进入和鼠标离开 (mousePressEvent) 实现通过事件获得鼠标点击的位置 (mouseReleaseEvent) 前一个的基础上添加鼠标释放事件 (mouseDoubleClickEvent) 鼠标双击事件 鼠标移动事件 鼠标滚轮事件 …...

stm32使用hal库模拟spi模式3
因为网上模拟spi模拟的都是模式0,很少有模式3的。 模式3的时序图,在clk的下降沿切换电平状态,在上升沿采样, SCK空闲为高电平 初始化cs,clk,miso,mosi四个io。miso配置为输入,cs、c…...
安装 Nginx
个人博客地址:安装 Nginx | 一张假钞的真实世界 对于 Linux 平台,Nginx 安装包 可以从 nginx.org 下载。 Ubuntu: 版本Codename支持平台12.04precisex86_64, i38614.04trustyx86_64, i386, aarch64/arm6415.10wilyx86_64, i386 在 Debian/Ubuntu 系统…...
Vue-1-前端框架Vue基础入门之一
文章目录 1 Vue简介1.1 Vue的特性1.2 Vue的版本2 Vue的基础应用2.1 Vue3的下载2.2 Vue3的新语法2.3 vue-devtools调试工具3 Vue的指令3.1 内容渲染指令{{}}3.2 属性绑定指令v-bind3.3 事件绑定指令v-on3.4 双向绑定指令v-model3.5 条件渲染指令v-if3.6 列表渲染指令v-for4 参考…...

OurBMC技术委员会2025年二季度例会顺利召开
5月28日,OurBMC社区技术委员会二季度例会顺利召开。本次会议采用线上线下结合的方式,各委员在会上听取了OurBMC社区二季度工作总结汇报,规划了2025年三季度的重点工作。 会上,技术委员会主席李煜汇报了社区2025年二季度主要工作及…...

postman自动化测试
目录 一、相关知识 1.网络协议 2.接口测试 3.编写测试用例 4.系统架构 二、如何请求 1.get请求 编辑2.post请求 3.用环境变量请求 4.Postman测试沙箱 一、相关知识 1.网络协议 规定数据信息发送与解析的方式。 网络传输协议 https相比http,信息在网…...

力扣热题100之二叉树的直径
题目 给你一棵二叉树的根节点,返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 代码 方法:递归 计算二叉树的直径可以理解…...

数字人技术的核心:AI与动作捕捉的双引擎驱动(210)
**摘要:**数字人技术从静态建模迈向动态交互,AI与动作捕捉技术的深度融合推动其智能化发展。尽管面临表情僵硬、动作脱节、交互机械等技术瓶颈,但通过多模态融合技术、轻量化动捕方案等创新,数字人正逐步实现自然交互与情感表达。…...
c++ 命名规则
目录 总结1. 类名(Class Names)2. 变量名(Variable Names)3. 函数名(Function Names)4. 宏定义(Macros)5. 命名空间(Namespaces)6. 枚举(Enums&am…...
GRU 参数梯度推导与梯度消失分析
GRU 参数梯度推导与梯度消失分析 1. GRU 前向计算回顾 GRU 单元的核心计算步骤(忽略偏置项): 更新门: z_t σ(W_z [h_{t-1}, x_t]) 重置门: r_t σ(W_r [h_{t-1}, x_t]) 候选状态: ̃h_t tanh(W_h [r_t ⊙ h_{t-1}, x_t]) 新…...

针对KG的神经符号集成综述 两篇
帖子最后有五篇综述的总结。 综述1 24年TKDD 系统性地概述了神经符号知识图谱推理领域的进展、技术和挑战。首先介绍了知识图谱(KGs)和符号逻辑的基本概念,知识图谱被视为表示、存储和有效管理知识的关键工具,它将现实世界的知识…...

RabbitMQ和MQTT区别与应用
RabbitMQ与MQTT深度解析:协议、代理、差异与应用场景 I. 引言 消息队列与物联网通信的重要性 在现代分布式系统和物联网(IoT)生态中,高效、可靠的通信机制是构建稳健、可扩展应用的核心。消息队列(Message Queues&am…...
Vue跨层级通信
下面,我们来系统的梳理关于 Vue跨层级通信 的基本知识点: 一、跨层级通信核心概念 1.1 什么是跨层级通信 跨层级通信是指在组件树中,祖先组件与后代组件(非直接父子关系)之间的数据传递和交互方式。这种通信模式避免了通过中间组件层层传递 props 的繁琐过程。 1.2 适用…...
docker常见命令行用法
🧨 一、关闭和清理 Docker 服务相关命令 🔻 docker-compose down 作用:关闭并删除所有使用当前 docker-compose.yml 启动的容器、网络、挂载卷(匿名的)、和依赖关系。 通俗解释:就像你关掉了一个 App&am…...

Axure设计案例:滑动拼图解锁
设计以直观易懂的操作方式为核心,只需通过简单的滑动动作,将拼图块精准移动至指定位置,即可完成解锁。这种操作模式既符合用户的日常操作习惯,在视觉呈现上,我们精心设计拼图图案,融入生动有趣的元素&#…...

MySQL权限详解
在MySQL中,权限管理是保障数据安全和合理使用的重要手段。MySQL提供了丰富的权限控制机制,允许管理员对不同用户授予不同级别的操作权限。本文将会对MySQL中的权限管理,以及内核如何实现权限控制进行介绍。 一、权限级别 MySQL 的权限是分层…...
基于BP神经网络的语音特征信号分类
基于BP神经网络的语音特征信号分类的MATLAB实现步骤: 1. 数据预处理 信号采样:读取语音信号并进行采样,确保信号具有统一的采样率。例如: [y, Fs] audioread(audio_file.wav); % 读取音频文件预加重:增强高频信号&am…...

解决fastadmin、uniapp打包上线H5项目路由冲突问题
FastAdmin 基于 ThinkPHP,默认采用 URL 路由模式(如 /index.php/module/controller/action),且前端资源通常部署在公共目录(如 public/)下。Uniapp 的历史模式需要将所有前端路由请求重定向到 index.html&a…...

web3-区块链的交互性以及编程的角度看待智能合约
web3-区块链的交互性以及编程的角度看待智能合约 跨链交互性 交互性 用户在某一区块链生态上拥有的资产和储备 目标:使用户能够把资产和储备移动到另一个区块链生态上 可组合性 使在某一区块链的DAPP能调用另一个区块链上的DAPP 如果全世界都在用以太坊就…...

数据结构(7)—— 二叉树(1)
目录 前言 一、 树概念及结构 1.1树的概念 1.2树的相关概念 1.3数的表示 1.二叉树表示 2.孩子兄弟表示法 3.动态数组存储 1.4树的实际应用 二、二叉树概念及结构 2.1概念 2.2特殊的二叉树 1.满二叉树 2. 完全二叉树 2.3二叉树的性质 2.4二叉树的存储结构 1.顺序存储 2.链式存储…...
ROS1和ROS2的区别autoware.ai和autoware.universe的区别
文章目录 前言一、ROS1和ROS2的区别一、ROS2通讯实时性比ROS1强二、ROS1官方不再维护了三、ROS2的可靠性比ros1强四、ROS2的安全性比ros1强五、ROS2资源占用低六、等等等等 二、autoware.ai和autoware.universe的区别一、autoware.ai不维护了二、autoware.universe功能多&#…...

如何使用 Docker 部署grafana和loki收集vllm日志?
环境: Ubuntu20.04 grafana loki 3.4.1 问题描述: 如何使用 Docker 部署grafana和loki收集vllm日志? 解决方案: 1.创建一个名为 loki 的目录。将 loki 设为当前工作目录: mkdir loki cd loki2.将以下命令复制并粘贴到您的命令行中,以将 loki-local-config.yaml …...

Kafka入门- 基础命令操作指南
基础命令 主题 参数含义–bootstrap-server连接的Broker主机名称以及端口号–topic操作的topic–create创建主题–delete删除主题–alter修改主题–list查看所有主题–describe查看主题的详细描述–partitions设置分区数–replication-factor设置分区副本–config更新系统默认…...