当前位置: 首页 > article >正文

深入解析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() 方法)

FutureTaskrun() 方法被调用时(通常由一个 Executor 的工作线程调用):

  1. 首先会通过 CAS 操作尝试将 runner 字段(volatile Thread runner)从 null 设置为当前线程。这确保了只有一个线程可以实际执行任务。

  2. 如果设置成功并且任务状态是 NEW,则会调用内部的 Callable 对象的 call() 方法。

  3. 如果 call() 方法正常返回,则调用 set(V result) 方法设置结果,并将状态转换为 NORMAL

  4. 如果 call() 方法抛出异常,则调用 setException(Throwable t) 方法设置异常,并将状态转换为 EXCEPTIONAL

  5. finally 块中,runner 字段会被重置为 null。还会检查任务是否在运行期间被取消并需要中断(状态为 INTERRUPTINGINTERRUPTED),如果是,则会调用 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 (包装了原始异常)。

    • CANCELLEDINTERRUPTED: 抛出 CancellationException

  • get(long, TimeUnit)** 方法**:类似 get(),但带有超时机制。如果在超时时间内任务未完成,则抛出 TimeoutException

4. 取消任务 (cancel(boolean mayInterruptIfRunning) 方法)

  1. 尝试通过 CAS 将状态从 NEW 转换为 CANCELLED (如果 mayInterruptIfRunningfalse) 或 INTERRUPTING (如果 mayInterruptIfRunningtrue)。

  2. 如果 CAS 成功:

    1. 如果 mayInterruptIfRunningtrue

      • 获取 runner 线程。

      • 如果 runner 不为 null,则调用 runner.interrupt() 来中断执行任务的线程。

      • finally 块中,将状态设置为 INTERRUPTED (使用 STATE.setRelease 保证内存可见性)。

    2. 最后,调用 finishCompletion() 来唤醒所有等待的线程。

  3. 如果 CAS 失败(例如任务已经完成或已被取消),则返回 false

5. 等待队列 (WaitNodewaiters 字段)

  • 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,然后在一个循环中:

  1. 通过 CAS 将新的 WaitNode 添加到 waiters 链表的头部(实现入栈)。

  2. 再次检查任务状态,如果已完成,则移除刚添加的节点并返回状态。

  3. 如果任务仍未完成,则调用 LockSupport.park(this) (或 LockSupport.parkNanos(this, nanos)) 使当前线程阻塞。

  4. 当线程被唤醒时,如果是因为中断,则从等待队列中移除节点并抛出 InterruptedException。如果是因为超时,则从等待队列中移除节点并返回当前状态。

  • finishCompletion()

当任务完成(通过 set, setException, 或 cancel)时,此方法被调用。

  1. 它会遍历 waiters 链表,并对每个 WaitNode 中的线程调用 LockSupport.unpark(q.thread) 来唤醒它们。

  2. 遍历完成后,调用 done() 方法(这是一个空方法,供子类覆盖以执行完成回调)。

  3. 最后将 callable 设为 null 以帮助 GC。

哪个线程负责管理唤醒 get 等待的线程?

负责唤醒等待线程的是完成任务的那个线程。具体来说:

  • 如果是任务正常执行完成或抛出异常,那么是执行 run() 方法的线程(即 runner 线程)在调用 set()setException() 后,最终会调用 finishCompletion() 来唤醒所有等待者。

  • 如果是任务被取消,那么是调用 cancel() 方法的线程在成功取消任务后,会调用 finishCompletion() 来唤醒所有等待者。

等待 get() 方法结果的线程被封装在 WaitNode 对象中。每个 WaitNode 包含:

  • volatile Thread thread;: 对等待线程本身的引用。

  • volatile WaitNode next;: 指向链表中下一个 WaitNode 的引用。

这些 WaitNode 对象形成一个后进先出 (LIFO) 的栈式链表,其头节点由 FutureTaskvolatile WaitNode waiters; 字段指向。当一个线程需要等待时,它会创建一个新的 WaitNode 并将其 CAS 到 waiters 链表的头部。当任务完成时,finishCompletion() 方法会遍历这个链表并唤醒每个节点中的线程。

设计优势

这种设计避免了使用更重的锁(如 AbstractQueuedSynchronizer,早期版本的 FutureTask 使用过它),转而使用轻量级的 CAS 操作和 LockSupport 进行线程的阻塞和唤醒,这在很多情况下能提供更好的性能。

相关文章:

深入解析FutureTask:原理与实战

我们来深入解析 FutureTask。下面将从它们的用法开始&#xff0c;逐步深入到底层实现、方法和接口设计&#xff0c;并探讨它们对于我们自己设计实现以及其他编程实践的学习意义。 主要就是放入等待队列&#xff08;CAS操作一个链表头&#xff09;&#xff0c;完成任务的线程唤…...

每天总结一个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 环境​​&#xff1a;安装 Node.js&#xff08;≥18.x&#xff09;、JDK&#xff08;≥11&#xff09;、Yarn。​​鸿蒙开发环境​​&#xff1a; 下载 DevEco Studio 4.0 及 HarmonyOS SDK&#xff1b;配…...

web3-Remix部署智能合约到“荷兰式”拍卖及以太坊gas费机制细讲

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

网络编程及原理(一)

目录 一 . 独立模式与网络互联 二 . 局域网 —— LAN &#xff08;1&#xff09;基于网线直连 &#xff08;2&#xff09;基于集线器组建 &#xff08;3&#xff09;基于交换机组建 &#xff08;4&#xff09;基于交换机和路由器组建 三 . 广域网 —— WAN 四 …...

superior哥AI系列第9期:高效训练与部署:从实验室到生产环境

&#x1f680; superior哥AI系列第9期&#xff1a;高效训练与部署&#xff1a;从实验室到生产环境 嘿&#xff01;小伙伴们&#xff01;&#x1f44b; 欢迎来到superior哥AI系列第9期&#xff01;经过前面8期的学习&#xff0c;你已经掌握了深度学习的核心技术。但是&#xff0…...

【Linux】进程 信号保存 信号处理 OS用户态/内核态

&#x1f33b;个人主页&#xff1a;路飞雪吖~ &#x1f320;专栏&#xff1a;Linux 目录 一、信号保存 ✨进程如何完成对信号的保存&#xff1f; ✨在内核中的表示 ✨sigset_t ✨信号操作函数 &#x1fa84;sigprocmask --- 获取或设置当前进程的 block表 &#x1fa84;s…...

[ Qt ] | 与系统相关的操作(一):鼠标相关事件

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

stm32使用hal库模拟spi模式3

因为网上模拟spi模拟的都是模式0&#xff0c;很少有模式3的。 模式3的时序图&#xff0c;在clk的下降沿切换电平状态&#xff0c;在上升沿采样&#xff0c; SCK空闲为高电平 初始化cs&#xff0c;clk&#xff0c;miso&#xff0c;mosi四个io。miso配置为输入&#xff0c;cs、c…...

安装 Nginx

个人博客地址&#xff1a;安装 Nginx | 一张假钞的真实世界 对于 Linux 平台&#xff0c;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日&#xff0c;OurBMC社区技术委员会二季度例会顺利召开。本次会议采用线上线下结合的方式&#xff0c;各委员在会上听取了OurBMC社区二季度工作总结汇报&#xff0c;规划了2025年三季度的重点工作。 会上&#xff0c;技术委员会主席李煜汇报了社区2025年二季度主要工作及…...

postman自动化测试

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

力扣热题100之二叉树的直径

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

数字人技术的核心:AI与动作捕捉的双引擎驱动(210)

**摘要&#xff1a;**数字人技术从静态建模迈向动态交互&#xff0c;AI与动作捕捉技术的深度融合推动其智能化发展。尽管面临表情僵硬、动作脱节、交互机械等技术瓶颈&#xff0c;但通过多模态融合技术、轻量化动捕方案等创新&#xff0c;数字人正逐步实现自然交互与情感表达。…...

c++ 命名规则

目录 总结1. 类名&#xff08;Class Names&#xff09;2. 变量名&#xff08;Variable Names&#xff09;3. 函数名&#xff08;Function Names&#xff09;4. 宏定义&#xff08;Macros&#xff09;5. 命名空间&#xff08;Namespaces&#xff09;6. 枚举&#xff08;Enums&am…...

GRU 参数梯度推导与梯度消失分析

GRU 参数梯度推导与梯度消失分析 1. GRU 前向计算回顾 GRU 单元的核心计算步骤&#xff08;忽略偏置项&#xff09;&#xff1a; 更新门: 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 系统性地概述了神经符号知识图谱推理领域的进展、技术和挑战。首先介绍了知识图谱&#xff08;KGs&#xff09;和符号逻辑的基本概念&#xff0c;知识图谱被视为表示、存储和有效管理知识的关键工具&#xff0c;它将现实世界的知识…...

RabbitMQ和MQTT区别与应用

RabbitMQ与MQTT深度解析&#xff1a;协议、代理、差异与应用场景 I. 引言 消息队列与物联网通信的重要性 在现代分布式系统和物联网&#xff08;IoT&#xff09;生态中&#xff0c;高效、可靠的通信机制是构建稳健、可扩展应用的核心。消息队列&#xff08;Message Queues&am…...

Vue跨层级通信

下面,我们来系统的梳理关于 Vue跨层级通信 的基本知识点: 一、跨层级通信核心概念 1.1 什么是跨层级通信 跨层级通信是指在组件树中,祖先组件与后代组件(非直接父子关系)之间的数据传递和交互方式。这种通信模式避免了通过中间组件层层传递 props 的繁琐过程。 1.2 适用…...

docker常见命令行用法

&#x1f9e8; 一、关闭和清理 Docker 服务相关命令 &#x1f53b; docker-compose down 作用&#xff1a;关闭并删除所有使用当前 docker-compose.yml 启动的容器、网络、挂载卷&#xff08;匿名的&#xff09;、和依赖关系。 通俗解释&#xff1a;就像你关掉了一个 App&am…...

Axure设计案例:滑动拼图解锁

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

MySQL权限详解

在MySQL中&#xff0c;权限管理是保障数据安全和合理使用的重要手段。MySQL提供了丰富的权限控制机制&#xff0c;允许管理员对不同用户授予不同级别的操作权限。本文将会对MySQL中的权限管理&#xff0c;以及内核如何实现权限控制进行介绍。 一、权限级别 MySQL 的权限是分层…...

基于BP神经网络的语音特征信号分类

基于BP神经网络的语音特征信号分类的MATLAB实现步骤&#xff1a; 1. 数据预处理 信号采样&#xff1a;读取语音信号并进行采样&#xff0c;确保信号具有统一的采样率。例如&#xff1a; [y, Fs] audioread(audio_file.wav); % 读取音频文件预加重&#xff1a;增强高频信号&am…...

解决fastadmin、uniapp打包上线H5项目路由冲突问题

FastAdmin 基于 ThinkPHP&#xff0c;默认采用 URL 路由模式&#xff08;如 /index.php/module/controller/action&#xff09;&#xff0c;且前端资源通常部署在公共目录&#xff08;如 public/&#xff09;下。Uniapp 的历史模式需要将所有前端路由请求重定向到 index.html&a…...

web3-区块链的交互性以及编程的角度看待智能合约

web3-区块链的交互性以及编程的角度看待智能合约 跨链交互性 交互性 用户在某一区块链生态上拥有的资产和储备 ​ 目标&#xff1a;使用户能够把资产和储备移动到另一个区块链生态上 可组合性 使在某一区块链的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更新系统默认…...