深入解析FutureTask:原理与实战

news2025/6/7 0:58:07

我们来深入解析 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 创建 FutureTask
        FutureTask<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,可以直接被线程执行或提交给 ExecutorService
        ExecutorService 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() 也为 true

        executor.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 进行线程的阻塞和唤醒,这在很多情况下能提供更好的性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2400610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每天总结一个html标签——Audio音频标签

Audio标签 文章目录 Audio标签一、audio标签的定义与介绍1. 定义介绍2. 语法3. 支持的格式4.文本提示 二、audio标签的HTML属性1. autoplay2. loop3. muted4. preload 三、audio标签的常用DOM属性四、audio标签的常用事件四、默认样式五、自定义样式1. 示例2. 代码 六、播放 m3…

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

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

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;数字人正逐步实现自然交互与情感表达。…

针对KG的神经符号集成综述 两篇

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

RabbitMQ和MQTT区别与应用

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

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

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

MySQL权限详解

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

解决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.链式存储…

如何使用 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更新系统默认…

目标检测我来惹1 R-CNN

目标检测算法&#xff1a; 识别图像中有哪些物体和位置 目标检测算法原理&#xff1a; 记住算法的识别流程、解决问题用到的关键技术 目标检测算法分类&#xff1a; 两阶段&#xff1a;先区域推荐ROI&#xff0c;再目标分类 region proposalCNN提取分类的目标检测框架 RC…