
教程技术博客文档【免费下载链接】YCBlogs技术博客笔记大汇总包括Java基础线程并发数据结构Android技术博客等等常用设计模式常见的算法网络协议知识点部分flutter笔记还包括平时开发中遇到的bug汇总当然也在工作之余收集了大量的面试题长期更新维护并且修正持续完善……开源的文件是markdown格式的转载请注明出处谢谢项目地址https://gitcode.com/gh_mirrors/yc/YCBlogs点击查看免费下载FutureTask 是 Java Executors 框架体系中用于表示可获取结果的异步任务的核心类它同时实现了Future与Runnable两个接口既能获取异步计算的结果又能作为任务提交给线程或线程池执行。本篇以 YCBlogs 仓库中的 FutureTask 简介 为骨架结合仓库内线程池、Callable 创建线程等关联文档深入讲解 FutureTask 的内部状态机、get()/cancel()在不同状态下的行为差异、三种基本使用方式及其典型应用场景读完即可在实战中正确使用 FutureTask 并理解其底层原理。01 FutureTask 是什么Executors 框架中的异步任务载体在 Executors 框架体系中FutureTask 用来表示可获取结果的异步任务。它实现了Future接口因此提供了如下能力启动通过run()方法异步任务取消异步任务cancel()方法查询异步任务是否计算结束isDone()、isCancelled()方法获取最终的异步任务结果get()方法。其中通过get()方法获取异步任务结果时会阻塞当前线程直至异步任务执行结束也支持传入超时时间的get(long timeout, TimeUnit unit)在超时后立即返回此时任务可能尚未执行完。从类层次结构看FutureTask 实现了RunnableFutureV接口而RunnableFuture同时继承了Runnable与FutureV。正因为如此FutureTask 可以交给Executor执行FutureTask 也可以由调用线程直接执行调用FutureTask.run()还可以通过ExecutorService.submit()方法返回一个 FutureTask实际返回Future对象再通过get()或cancel()操作它。这一点与仓库 线程池复用原理 中execute 与 submit 的区别相呼应submit之所以能拿到返回结果、能判断任务是否执行成功正是因为它内部将任务包装成了FutureTask并返回对应的Future对象。02 FutureTask 内部状态机7 种状态常量FutureTask 在源码中为自身定义了如下 7 种状态原文完整保留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; // 已中断这 7 个状态常量是整个 FutureTask 并发正确性的基石所有状态转换都基于 CAS 操作完成从而保证多个线程并发操作同一个 FutureTask 时状态一致。状态迁移的大致路径为正常路径NEW - COMPLETING - NORMAL异常路径NEW - COMPLETING - EXCEPTIONALcall()抛出异常时结果会被封装为ExecutionException抛出取消路径NEW - CANCELLED无中断取消、NEW - INTERRUPTING - INTERRUPTED中断式取消。03 从执行时机看 FutureTask三阶段运行状态《Java 并发编程的艺术》一书从FutureTask.run()方法的执行时机出发将 FutureTask 归纳为 3 种运行状态这是理解get()与cancel()行为差异最直观的视角未启动FutureTask.run()方法还没有被执行之前。即创建出 FutureTask、但尚未执行run()时FutureTask 处于未启动状态。已启动FutureTask.run()方法被执行的过程中。已完成FutureTask.run()方法执行结束、或者调用FutureTask.cancel(...)取消任务、或者在执行任务期间抛出异常以上任一情况发生都视为 FutureTask 处于已完成状态。注意区分源码层面的 7 个状态常量描述的是内部精确状态而这里的三阶段描述的是对外可感知的运行阶段两者本质上是同一件事在不同粒度上的表达——例如已完成阶段涵盖了NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTED等终态。04 get() 与 cancel() 在不同状态下的行为差异由于 FutureTask 具有上述三种运行状态执行get()和cancel()方法时当前处于不同状态对应的结果大不相同。这也是面试中高频考察的知识点整理如下。4.1 get() 方法FutureTask 状态get()行为未启动调用线程被阻塞直到任务启动并执行完成已启动调用线程被阻塞直到任务执行完成已完成调用线程立即返回结果NORMAL或立即抛出异常EXCEPTIONAL时抛出ExecutionExceptionCANCELLED/INTERRUPTED时抛出CancellationException补充两点实战细节若不想无限期阻塞可使用get(long timeout, TimeUnit unit)超时后抛出TimeoutException此时任务可能仍在后台执行线程池场景下submit()返回的 Future 在任务完成前调用get()同样会阻塞调用线程参见仓库 线程池基础介绍 中对submit的说明如果子线程任务没有完成get 方法会阻塞住直到任务完成。4.2 cancel() 方法FutureTask 状态cancel()行为未启动执行cancel()后此任务将永远不会执行返回true已启动cancel(true)以中断线程的方式来阻止任务继续进行cancel(false)不会对正在执行任务的线程产生任何影响已完成执行cancel(...)返回false任务结果已经产生无法取消这里需要强调一个关键点cancel(true)依赖线程的中断机制。如果任务内部不响应中断例如不检查Thread.currentThread().isInterrupted()或执行阻塞操作时不处理InterruptedException中断信号可能无法真正停止任务。仓库 线程池的基础介绍 在讲解shutdownNow()时也印证了这一点中断采用 interrupt 方法所以无法响应中断的任务可能永远无法终止。另外一旦任务执行结束处于已完成状态任务不能重新启动或取消除非调用runAndReset()方法——该方法执行完任务后会重置状态使 FutureTask 可以被再次执行这正是后文ScheduledThreadPoolExecutor实现周期任务的基础。05 FutureTask 基本使用三种提交方式FutureTask 同时实现了Future接口和Runnable接口因此使用方式非常灵活常见的有以下三种。5.1 方式一包装 Callable 后交给 Thread 直接执行这是最基础的使用方式步骤为创建Callable接口的实现类实现call()方法该方法将作为线程执行体并且有返回值创建 Callable 实现类的实例使用FutureTask类来包装 Callable 对象该 FutureTask 对象封装了 Callable 对象的call()方法的返回值使用 FutureTask 对象作为Thread对象的 target 创建并启动新线程调用FutureTask.get()方法获得子线程执行结束后的返回值调用get()会阻塞线程。仓库 线程Api深度原理分析 给出了完整可运行的示例这里直接引用public class CallableThread implements CallableInteger { Override public Integer call() throws Exception { int i 0; for (; i 100; i) { System.out.println(Thread.currentThread().getName() i); } return i; } } public class ThreadDemo { public static void main(String[] args) { CallableThread ctt new CallableThread(); FutureTaskInteger ft new FutureTask(ctt); for (int i 0; i 100; i) { System.out.println(Thread.currentThread().getName() 的循环变量i的值 i); if (i 20) { new Thread(ft, 有返回值的线程).start(); } } try { System.out.println(子线程的返回值 ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }注意捕获的两种异常InterruptedException当前线程在等待结果时被中断和ExecutionException任务内部call()抛出的异常被包装后抛出。5.2 方式二交给线程池执行submitFutureTask 可以交给Executor执行也可以通过ExecutorService.submit()提交并获得 Future 对象ExecutorService service new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue()); FutureInteger future service.submit(new CallableInteger() { Override public Integer call() throws Exception { System.out.println(submit方式); return 2; } }); try { Integer number future.get(); } catch (ExecutionException e) { e.printStackTrace(); }上述线程池构造与submit/get用法出自仓库 线程池的基础介绍。需要理解的是用execute提交任务时没有返回值无法判定任务是否执行成功用submit提交任务时返回一个 Future通过它可以判断任务是否执行成功、获取返回值get()会阻塞直到任务完成get(long timeout, TimeUnit unit)则阻塞一段时间后立即返回此时任务可能没有执行完。从源码实现看submit最终也会调用execute区别只在于提交前将 Runnable/Callable 包装成了 FutureTask参见仓库 线程池复用原理 对 execute 与 submit 的对比分析。5.3 方式三调用线程直接执行 run()由于 FutureTask 本身实现了 Runnable也可以不借助任何线程直接在调用线程上同步执行FutureTaskInteger task new FutureTask(() - 42); task.run(); // 同步执行 System.out.println(task.get()); // 输出 42这种用法较少但体现了FutureTask 既可以异步执行也可以同步执行的灵活性。5.4 前置知识Callable 与 Runnable 的区别使用 FutureTask 通常离不开Callable。仓库 Java多线程问题汇总 对两者差异总结得很清晰Runnable接口中的run()方法返回值是void只是纯粹地执行代码Callable接口中的call()方法有返回值泛型V与Future、FutureTask配合可以获取异步执行的结果call()方法可以抛出异常run()方法不可以运行 Callable 任务可以拿到一个Future对象表示异步计算的结果通过它既可以了解任务执行情况、取消任务执行还可以获取执行结果。public interface Runnable { public void run(); } public interface CallableV { V call() throws Exception; }之所以Callable Future/FutureTask 非常有用是因为多线程相比单线程充满未知性——某条线程是否执行了执行了多久期望的数据是否已经赋值完毕这些都是未知的只能等待任务执行完毕。而 FutureTask 恰好解决了等待 取结果 必要时取消这一整套诉求。06 典型应用场景同一任务只执行一次原文档给出了 FutureTask 最典型的应用场景可归纳为两类场景一任务依赖等待。当一个线程需要等待另一个线程把某个任务执行完后才能继续执行此时可以使用 FutureTask。get()的阻塞特性天然承担了等待的职责。场景二多线程竞争同一任务只允许执行一次。假设有多个线程执行若干任务每个任务最多只能被执行一次。当多个线程试图执行同一个任务时只允许一个线程执行任务其他线程需要等待这个任务执行完后才能继续执行。场景二之所以成立根源在于 FutureTask 的内部实现多个线程可以共享同一个 FutureTask 实例因为它实现了 Runnablerun()方法内部会通过 CAS 将状态从NEW迁移只有成功完成迁移的线程才真正执行任务体其余线程进入等待/直接读取结果状态一旦进入终态NORMAL/EXCEPTIONAL/CANCELLED等后续线程调用run()不再重复执行任务体调用get()直接获得已有结果。这种共享任务、单次执行的语义也正好呼应了仓库 线程池复用原理 中多个相同线程处理同一份资源的设计思想——多个 Worker 线程可以从队列中取到任务但任务的执行结果由 FutureTask 统一托管。07 从源码看 FutureTask 在 Java 并发体系中的位置结合仓库其他文档可以进一步看清 FutureTask 在整个 Java 并发体系中的枢纽地位。7.1 ThreadPoolExecutor 中的任务统一为 FutureTask在ThreadPoolExecutor中通过submit提交的任务会被包装为FutureTask类而在ScheduledThreadPoolExecutor中任务被转换为ScheduledFutureTask类——该类继承自 FutureTask并重写了run方法详见仓库 ScheduledThreadPoolExecutor 分析。7.2 runAndReset() 与周期任务的实现ScheduledFutureTask重写run()方法时正是借助了 FutureTask 的runAndReset()来实现周期执行public void run() { boolean periodic isPeriodic(); if (!canRunInCurrentRunState(periodic)) cancel(false); else if (!periodic) ScheduledFutureTask.super.run(); // 非周期任务直接执行 else if (ScheduledFutureTask.super.runAndReset()) { // 周期任务执行并重置状态 setNextRunTime(); // 重设下一次执行时间 reExecutePeriodic(outerTask); // 将下一次任务放入 DelayedWorkQueue } }这段源码直观展示了runAndReset()的价值普通 FutureTask 执行结束后进入终态、不可复用而runAndReset()在执行完任务后重置状态使任务可以被再次放入队列执行——这正是周期性任务得以实现的关键。7.3 状态设计与并发安全FutureTask 的 7 种状态配合 CAS 操作保证了多线程场景下的可见性与原子性任务的执行者线程池 Worker 或直接启动的 Thread负责状态推进等待结果的线程通过get()阻塞或轮询状态取消方通过cancel()尝试推进到取消态。这种状态机 CAS的设计思路与仓库 Java并发 系列文档中关于 CAS、锁与原子性的讨论一脉相承也是理解 AQS 等更底层并发组件的基础。08 小结定位FutureTask 是 Executors 框架中可获取结果的异步任务实现FutureRunnable既能取结果又能当任务提交执行状态内部 7 种状态常量NEW、COMPLETING、NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTING、INTERRUPTED对外可归纳为未启动、已启动、已完成三阶段get()未启动/已启动时阻塞等待已完成时立即返回结果或抛出异常cancel()未启动时使任务永不执行已启动时cancel(true)中断线程、cancel(false)不生效已完成时返回false使用可包装 Callable 交给 Thread 直接执行、交给线程池submit执行或调用线程直接run()应用线程间结果等待、多线程竞争同一任务只执行一次runAndReset()更是ScheduledThreadPoolExecutor周期任务的基石。更多相关主题可继续阅读仓库内的 线程池基础介绍、线程池复用原理、ScheduledThreadPoolExecutor 分析 以及 线程Api深度原理分析形成完整的线程知识体系。赞分享教程技术博客文档【免费下载链接】YCBlogs技术博客笔记大汇总包括Java基础线程并发数据结构Android技术博客等等常用设计模式常见的算法网络协议知识点部分flutter笔记还包括平时开发中遇到的bug汇总当然也在工作之余收集了大量的面试题长期更新维护并且修正持续完善……开源的文件是markdown格式的转载请注明出处谢谢项目地址https://gitcode.com/gh_mirrors/yc/YCBlogs点击查看免费下载相关推荐Quickemu3分钟快速创建Windows、macOS和Linux虚拟机Quickemu3分钟快速创建Windows、macOS和Linux虚拟机 Quickemu 是一个基于QEMU的虚拟机管理工具能够快速创建和运行经过优化的虚拟化CLIAutoGluon 目标检测数据准备完全指南COCO/VOC 数据集下载、格式转换与 DataFrame 训练AutoGluon 目标检测数据准备完全指南COCO/VOC 数据集下载、格式转换与 DataFrame 训练 本文整理自 Object Detection教程技术博客文档免费窗口管理工具 Loop径向菜单分屏从安装到熟练的完整指南免费窗口管理工具 Loop径向菜单分屏从安装到熟练的完整指南 下午三点你在编辑器、终端和浏览器之间来回切了 5 次想把终端贴到屏幕右半边鼠标拖了 3 次桌面应用上一篇Blender 3MF格式插件连接3D设计与专业打印的桥梁下一篇cppcheck 检查器剖析copyCtorNoAllocation —— 已注册但永不触发的“死代码”检查器及其浅拷贝防线创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考