Java多线程与高并发专题——使用 Future 有哪些注意点?Future 产生新的线程了吗?

Future 的注意点

1. 当 for 循环批量获取 Future 的结果时容易 block,get 方法调用时应使用 timeout 限制

对于 Future 而言,第一个注意点就是,当 for 循环批量获取 Future 的结果时容易 block,在调用 get方法时,应该使用 timeout 来限制。

下面我们具体看看这是一个什么情况。

首先,假设一共有四个任务需要执行,我们都把它放到线程池中,然后它获取的时候是按照从 1 到 4 的顺序,也就是执行 get() 方法来获取的,代码如下所示:

/*** 这个类演示了如何使用 Java 的 Future 和 Callable 接口来执行异步任务。* 它创建了一个固定大小的线程池,并提交了多个任务,其中一些任务执行速度较慢,而另一些任务执行速度较快。* 最后,它等待所有任务完成并打印出每个任务的结果。*/
public class FutureDemo {/*** 程序的入口点,演示了如何使用 Future 和 Callable 接口执行异步任务。* * @param args 命令行参数(未使用)*/public static void main(String[] args) {// 创建一个固定大小为 10 的线程池ExecutorService service = Executors.newFixedThreadPool(10);// 用于存储所有 Future 对象的列表ArrayList<Future> allFutures = new ArrayList<>();// 循环提交 4 个任务for (int i = 0; i < 4; i++) {Future<String> future;// 根据 i 的值选择提交 SlowTask 或 FastTaskif (i == 0 || i == 1) {// 提交 SlowTask 任务future = service.submit(new SlowTask());} else {// 提交 FastTask 任务future = service.submit(new FastTask());}// 将 Future 对象添加到列表中allFutures.add(future);}// 遍历所有 Future 对象并获取结果for (int i = 0; i < 4; i++) {// 从列表中获取 Future 对象Future<String> future = allFutures.get(i);try {// 获取任务的结果String result = future.get();// 打印任务的结果System.out.println(result);} catch (InterruptedException e) {// 处理线程中断异常e.printStackTrace();} catch (ExecutionException e) {// 处理任务执行异常e.printStackTrace();}}// 关闭线程池service.shutdown();}/*** 表示一个执行速度较慢的任务。* 实现了 Callable 接口,返回一个字符串结果。*/static class SlowTask implements Callable<String> {/*** 执行任务的主要逻辑。* 线程会休眠 5 秒钟,然后返回一个表示任务完成的字符串。* * @return 表示任务完成的字符串* @throws Exception 如果线程在休眠期间被中断*/@Overridepublic String call() throws Exception {// 线程休眠 5 秒钟Thread.sleep(5000);// 返回任务结果return "速度慢的任务";}}/*** 表示一个执行速度较快的任务。* 实现了 Callable 接口,返回一个字符串结果。*/static class FastTask implements Callable<String> {/*** 执行任务的主要逻辑。* 立即返回一个表示任务完成的字符串。* * @return 表示任务完成的字符串* @throws Exception 如果发生异常*/@Overridepublic String call() throws Exception {// 返回任务结果return "速度快的任务";}}
}

可以看出,在代码中我们新建了线程池,并且用一个 list 来保存 4 个 Future。其中,前两个 Future 所对应的任务是慢任务,也就是代码下方的 SlowTask,而后两个 Future 对应的任务是快任务。慢任务在执行的时候需要 5 秒钟的时间才能执行完毕,而快任务很快就可以执行完毕,几乎不花费时间。

在提交完这 4 个任务之后,我们用 for 循环对它们依次执行 get 方法,来获取它们的执行结果,然后再把这个结果打印出来。

执行结果如下:

可以看到,这个执行结果是打印 4 行语句,前面两个是速度慢的任务,后面两个是速度快的任务。虽然结果是正确的,但实际上在执行的时候会先等待 5 秒,然后再很快打印出这 4 行语句。

这里有一个问题,即第三个的任务量是比较小的,它可以很快返回结果,紧接着第四个任务也会返回结果。但是由于前两个任务速度很慢,所以我们在利用 get 方法执行时,会卡在第一个任务上。也就是说,虽然此时第三个和第四个任务很早就得到结果了,但我们在此时使用这种 for 循环的方式去获取结果,依然无法及时获取到第三个和第四个任务的结果。直到 5 秒后,第一个任务出结果了,我们才能获取到,紧接着也可以获取到第二个任务的结果,然后才轮到第三、第四个任务。

假设由于网络原因,第一个任务可能长达 1 分钟都没办法返回结果,那么这个时候,我们的主线程会一直卡着,影响了程序的运行效率。

此时我们就可以用 Future 的带超时参数的 get(long timeout, TimeUnit unit) 方法来解决这个问题。这个方法的作用是,如果在限定的时间内没能返回结果的话,那么便会抛出一个TimeoutException 异常,随后就可以把这个异常捕获住,或者是再往上抛出去,这样就不会一直卡着了。

2. Future 的生命周期不能后退

Future 的生命周期不能后退,一旦完成了任务,它就永久停在了“已完成”的状态,不能从头再来,也不能让一个已经完成计算的 Future 再次重新执行任务。

这一点和线程、线程池的状态是一样的,线程和线程池的状态也是不能后退的。关于线程的状态和流转路径,在线程状态与线程停止已经讲过了,如图所示。

这个图也是我们当时讲解所用的图,如果有些遗忘,可以回去复习一下当时的内容。

注意点:

  1. 检查任务是否完成
    使用 isDone() 方法检查任务是否完成,避免不必要的等待。
  2. 避免长时间阻塞
    使用 get() 方法会阻塞当前线程,直到任务完成。为了避免长时间阻塞,可以使用 get(long timeout, TimeUnit unit) 方法,并设置超时时间。
  3. 处理异常
    如果任务抛出异常,get() 方法会抛出 ExecutionException,其 cause 是任务抛出的异常。需要妥善处理这些异常。
  4. 取消任务
    如果任务尚未完成,可以使用 cancel(boolean mayInterruptIfRunning) 方法取消任务。如果任务已经完成或被取消,cancel 方法将返回 false。
  5. 资源管理
    确保在任务完成后释放相关资源,避免内存泄漏。
  6. 线程安全
    Future 本身是线程安全的,但任务的执行结果需要确保线程安全,尤其是在多个线程访问共享资源时。
  7. 避免直接使用 Thread
    推荐使用 ExecutorService 来管理线程池,而不是直接使用 Thread 创建线程。

Future 产生新的线程了吗

最后我们再来回答这个问题:Future 是否产生新的线程了?

有一种说法是,除了继承 Thread 类和实现 Runnable 接口之外,还有第三种产生新线程的方式,那就是采用 Callable 和 Future,这叫作有返回值的创建线程的方式。这种说法是不正确的。

其实 Callable 和 Future 本身并不能产生新的线程,它们需要借助其他的比如 Thread 类或者线程池才能执行任务。例如,在把 Callable 提交到线程池后,真正执行 Callable 的其实还是线程池中的线程,而线程池中的线程是由 ThreadFactory 产生的,这里产生的新线程与 Callable、Future 都没有关系,所以Future 并没有产生新的线程。

Future 本身并不直接创建新的线程。它只是一个接口,表示异步计算的结果。线程的创建和管理通常由 ExecutorService 或其他执行器来完成。

Future 的作用及特点:

  • Future 主要用于获取异步任务的结果。当一个异步任务被提交执行后,可以通过 Future 来获取任务的结果,而无需阻塞当前线程等待任务完成。例如,使用 Java 的java.util.concurrent.ExecutorService提交一个任务时,会返回一个 Future 对象。这个对象可以在稍后的时间用来检查任务是否完成,以及获取任务的结果。
  • Future 并不负责创建线程,它只是对异步任务的结果进行包装和管理。异步任务的执行通常是由线程池中的线程来完成的,而线程池在创建时就已经包含了一定数量的线程,这些线程会被重复利用来执行提交的任务。所以,Future 是利用已有的线程资源来管理异步任务,而不是创建新的线程。

线程的创建方式与 Future 的关系:

  • 在 Java 中,创建新线程主要有两种方式。一种是继承java.lang.Thread类并重写run方法,另一种是实现java.lang.Runnable接口并将其作为参数传递给Thread构造函数或ExecutorService的submit方法。无论是哪种方式,都是明确地创建新的线程来执行特定的任务。
  • 而 Future 通常是在使用线程池执行异步任务时返回的对象。线程池中的线程会执行提交的任务,任务完成后,Future 对象可以用来获取任务的结果。所以,Future 是与线程池中的线程协作,而不是直接创建新线程。

示例说明

以下是一个使用线程池和 Future 的示例代码:

/*** 该类演示了如何使用 Java 的 Future 接口来执行异步任务。* 通过 ExecutorService 提交一个异步任务,并使用 Future 对象获取任务的结果。*/
public class FutureExample {/*** 程序的入口点,演示了如何使用 ExecutorService 和 Future 来执行异步任务。** @param args 命令行参数* @throws InterruptedException 如果在等待异步任务完成时线程被中断* @throws ExecutionException 如果异步任务执行过程中抛出异常*/public static void main(String[] args) throws InterruptedException, ExecutionException {// 创建一个固定大小为 5 的线程池ExecutorService executorService = Executors.newFixedThreadPool(5);// 提交一个异步任务到线程池,并返回一个 Future 对象Future<Integer> future = executorService.submit(() -> {// 这里是异步任务的执行逻辑System.out.println("异步任务正在执行...");// 模拟耗时操作,线程休眠 2 秒Thread.sleep(2000);// 返回异步任务的结果return 42;});// 主线程继续执行其他任务System.out.println("主线程继续执行其他任务...");// 获取异步任务的结果,如果任务未完成,会阻塞当前线程直到任务完成Integer result = future.get();// 输出异步任务的结果System.out.println("异步任务结果:" + result);// 关闭线程池executorService.shutdown();}
}

在这个例子中,ExecutorService的submit方法提交了一个异步任务,返回一个 Future 对象。这个异步任务是在线程池中的某个线程中执行的,而不是由 Future 创建新线程来执行。主线程可以继续执行其他任务,然后通过 Future 的get方法获取异步任务的结果。如果异步任务未完成,get方法会阻塞主线程直到任务完成。

综上所述,Java 中的 Future 本身不会产生新的线程,它主要是用于管理异步任务的结果,与线程池中的线程协作来实现异步计算。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32基础教程——PWM驱动LED呼吸灯

目录 前言 技术实现 原理图 接线图 代码实现 内容要点 PWM基本结构 开启外设时钟 配置GPIO端口 配置时基单元 初始化输出比较单元 输出PWM波形 输出比较通道重映射 前言 PWM(Pulse Width Modulation):一种通过调节脉冲信号的占空比&#xff08;高电平持续时间与整…

算法基础——栈

一、栈的概念 栈是⼀种只允许在⼀端进⾏数据插⼊和删除操作的线性表。 进⾏数据插⼊或删除的⼀端称为栈顶&#xff0c;另⼀端称为栈底。不含元素的栈称为空栈。进栈就是往栈中放⼊元素&#xff0c;出栈就是将元素弹出栈顶。 二、栈的模拟实现 1. 创建 本质还是线性表&#…

JVM类文件结构详解

文章目录 前言代码示例1.魔数2.版本3.常量池4.访问标识与继承信息访问标识继承信息 5.Field 信息6 Method 信息构造方法分析(method-main)main方法分析(method-main) 7.附加属性 前言 在 Java 中&#xff0c;JVM 可以理解的代码就叫做字节码&#xff08;即扩展名为 .class 的文…

5.安全相关(双手启动、安全触边传感器)

一、关于双手启动按钮的使用规范 本文介绍双手启动按钮的使用。概括来讲&#xff1a; 双手按下之间的时间差间隔应该在0.5-2秒之间。一旦释放任何一个按钮&#xff0c;启动信号输出结束。只有两个按钮都被释放之后&#xff0c;才能再次触发双手启动信号。如果某按钮被按下超过…

电脑上不了网普通用户排除方法

1&#xff1a;首先通过电脑的运行/CMD/ipconfig /all 命令查看电脑的ip地址是否正常如图&#xff1a; 2&#xff1a;在命令行中运行&#xff1a;ping 127.0.0.1 如图则正常&#xff0c;否则要重新安装网卡驱动 程序。 3&#xff1a;用ping命令&#xff0c;ping一下同网段的电…

【中文翻译】第3章(1/3)-The Algorithmic Foundations of Differential Privacy

为方便阅读&#xff0c;故将《The Algorithmic Foundations of Differential Privacy》翻译项目内容搬运至此&#xff1b; 教材原文地址&#xff1a;https://www.cis.upenn.edu/~aaroth/Papers/privacybook.pdf 中文翻译版 Github 项目地址1&#xff1a;https://github.com/gu…

2.1词法分析任务

编译器结构 前端 词法分析器的任务 字符流到记号流 eg: 把这些单词就叫做记号 EOF结束符号&#xff01;

Linux操作系统7- 线程同步与互斥5(POSIX条件变量生产者消费者模型的进一步使用)

上篇文章&#xff1a;Linux操作系统7- 线程同步与互斥4&#xff08;基于POSIX条件变量的生产者消费者模型&#xff09;-CSDN博客 本篇代码仓库&#xff1a; 支持处理简单任务的生产者消费者模型代码 生产者-消费者-保存者三线程两队列模型 多生产多消费的生产者消费者模型 进一…

【嵌入式学习2】C语言 - VScode环境搭建

目录 ## 语言分类 ## c语言编译器 ## VScode相关配置 ## 语言分类 编译型语言&#xff1a;C&#xff0c;C解释型语言&#xff1a;python&#xff0c;JS ## c语言编译器 分类GCC 系列MinGWCygwinMSVC系列一套编程语言编译器将GCC编译器和GNU Binutils移植到Win32平台下的产物…

使用Doris broker load导入数据到带Kerberos的HA HDFS的命令详解

以下是关于 Doris Broker Load 结合 Kerberos 认证的 HDFS 数据导入的详细解析&#xff1a; 一、Broker Load 核心原理 Broker Load 是 Doris 中用于从 HDFS/S3 等远程存储系统异步导入大数据的工具&#xff0c;其核心流程如下&#xff1a; 任务提交&#xff1a;用户通过 SQL…

数字化转型 2.0:AI、低代码与智能分析如何重塑企业竞争力?

引言&#xff1a;数字化转型进入2.0时代 在过去的十几年里&#xff0c;企业的数字化转型&#xff08;1.0&#xff09;主要围绕信息化和自动化展开&#xff0c;例如引入ERP、CRM等系统&#xff0c;提高办公效率&#xff0c;减少人为失误。然而&#xff0c;随着市场竞争加剧&…

指针,数组 易混题解析(一)

目录 一.相关知识点 1.数组名是什么&#xff1f; 两个例外&#xff1a; 2.strlen 3.sizeof 4. * ( ) 与 [ ] 的互换 二.一维数组 三.字符数组 1. 字符 &#xff08;1&#xff09;sizeof &#xff08;2&#xff09;strlen 2.字符串 &#xff08;1&#xff09;si…

ABC392题解

A 算法标签: 模拟 #include <iostream> #include <algorithm> #include <cstring>using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int w[3];for (int i 0; i < 3; i) cin >> w[i];sort(w, w 3);if (w[0]…

Quartus + VScode 实现模块化流水灯

文章目录 一、通过VScode编写Verilog代码二、模块化编程三、代码示例 一、通过VScode编写Verilog代码 1、下载Vscode 2、下载相关插件 搜索Verilog就会弹出有如图所示的插件&#xff0c;下载并安装 3、创建Quartus项目 4、创建完成后点击Tools&#xff0c;选择Options 然后在…

简单讲一下控制系统所用的PID公式

2025年3月23日电子电工实验室讲课大纲 哈喽&#xff0c;小伙伴们大家好&#xff0c;今天我们来讲一下PID&#xff01;首先我们的针对的场景是什么——循迹小车&#xff01; 就是我们刚入手STM32时&#xff0c;我们可能会制作一个循迹小车。我们想让那个小车走直线&#xff0c;但…

直观理解ECC椭圆曲线加密算法

数学还是挺有逻辑的&#xff0c;给出计算的操作步骤 就能得出想要结果 背景&#xff1a; ● ECC 是一种极其巧妙的 非对称加密算法 , 其完美利用了 椭圆曲线几何累加 不可逆的性质 ● 拥有 密钥体积小&#xff0c;计算速度快的优势&#xff0c;被广泛用于各种区块链&#xff0c…

深度解析 Android Matrix 变换(二):组合变换 pre、post

前言 在上一篇文章中&#xff0c;我们讲解了 Canvas 中单个变换的原理和效果&#xff0c;即缩放、旋转和平移。但是单个旋转仅仅是基础&#xff0c;Canvas 变换最重要的是能够随意组合各种变换以实现想要的效果。在这种情况下&#xff0c;就需要了解如何组合变换&#xff0c;以…

c++之迭代器

一.迭代器的基本概念 1.什么是迭代器 迭代器是一种对象&#xff0c;它提供了一种访问容器中各个元素的方法&#xff0c;同时隐藏了容器内部的实现细节。简单来说&#xff0c;迭代器就像是一个指针&#xff0c;它可以指向容器中的某个元素&#xff0c;并且能够通过一些操作&am…

在 .NET 9.0 Web API 中实现 Scalar 接口文档及JWT集成

示例代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/90408075 介绍 随着 .NET 9 的发布&#xff0c;微软宣布他们将不再为任何 .NET API 项目提供默认的 Swagger gen UI。以前&#xff0c;当我们创建 .NET API 项目时&#xff0c;微软会自动添加 Swagger…

【操作系统笔记】操作系统的功能

上节课,我们学习了《什么是操作系统》。接下来,我们来看看操作系统有哪些功能? 这里讲的内容有两部分,一个是操作系统的目标,另外一个就是操作系统的功能。这两个细节可能会在考试的时候考到,但是最近好些年很少考到了。为了理解,我们还是一起来看一下。 操作系统的目标…