多线程系列(十九) -Future使用详解
一、摘要
在前几篇线程系列文章中,我们介绍了线程池的相关技术,任务执行类只需要实现Runnable
接口,然后交给线程池,就可以轻松的实现异步执行多个任务的目标,提升程序的执行效率,比如如下异步执行任务下载。
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 提交任务
executor.submit(new Runnable() {
@Override
public void run() {
// 执行下载某文件任务
System.out.println("执行下载某文件任务");
}
});
而实际上Runnable
接口并不能满足所有的需求,比如有些场景下,我们想要获取任务执行的返回结果,Runnable
接口因为无返回值,只能想办法通过额外的方式来写入和读取,操作起来十分不便。
因此,从 JDK 1.5 开始,Java 标准库提供了一个Callable
接口,与Runnable
接口相比,它的方法上多了一个返回值;同时Callable
是一个泛型接口,可以返回指定类型的结果,比如如下的实现类!
public class Task implements Callable<String> {
@Override
public String call() throws Exception {
// 执行下载某文件任务
System.out.println("执行下载某文件任务");
return "xxx";
}
}
问题来了,如何获取异步执行的结果呢?
在 JDK 1.5 中,Java 标准库还提供了一个Future
接口,它可以用来获取异步执行的结果。
下面我们一起来了解一下这个Future
接口!
二、Future
Future
接口,表示一个可能还没有完成异步任务的结果,它提供了检查任务是否已完成、以及等待任务完成并获取结果等方法。
如果看过ExecutorService.submit()
方法,会发现它的返回参数都是Future
类型,Future
类型的实例可以用来获取异步任务执行的结果。
下面我们先来看一个简单的示例,以便于更好的理解!
public class Task implements Callable<String> {
@Override
public String call() throws Exception {
// 执行下载某文件任务,并返回文件名称
System.out.println("thread name:" + Thread.currentThread().getName() + " 开始执行下载任务");
return "xxx.png";
}
}
public class FutureTest {
public static void main(String[] args) throws Exception {
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 初始化一个任务
Callable<String> task = new Task();
// 提交任务并获得Future的实例
Future<String> future = executor.submit(task);
// 从Future获取异步执行返回的结果(可能会阻塞等待结果)
String result =future.get();
System.out.println("任务执行结果:" + result);
// 任务执行完毕之后,关闭线程池(可选)
executor.shutdown();
}
}
输出结果如下:
thread name:pool-1-thread-1 开始执行下载任务
任务执行结果:xxx.png
从以上的示例可以清晰的看到,当需要获取异步线程的执行结果返回值时,通常需要搭配使用Future
和Callable
接口来实现,大体可以用如下步骤来概括:
- 1.首先提交一个实现
Callable
接口的任务到线程池中 - 2.然后获取一个
Future
类型的对象 - 3.最后在主线程中调用
Future
对象的get()
方法,如果异步任务执行完成,就可以直接获得结果;如果异步任务执行没有完成,get()
方法会阻塞,直到任务执行完成后才能获取结果
分析源码你会发现,Callable
接口主要用途是定义一个支持返回结果的方法;重点实现主要集中在Future
接口上。
下面我们重点来看下Future
接口方法!
2.1、Future 接口方法
方法 | 描述 |
---|---|
get() |
获取结果(会阻塞等待) |
get(long timeout, TimeUnit unit) |
在指定的时间内获取结果,如果超时,会抛异常并退出等待状态 |
cancel(boolean mayInterruptIfRunning) |
尝试取消当前任务,当传入参数为true 时,表示尝试中断任务的执行,false 表示不中断,继续执行直到完成,如果取消成功,返回true ;反之false |
isCancelled() |
判断任务是否已取消 |
isDone() |
判断任务是否已完成 |
2.2、Future 接口实现类
Future
本质其实是一个接口,并不是具体的实现类,真正负责工作的还是它的实现类来完成。
我们还是以上文的线程池ExecutorService.submit()
方法为例,看看它用的是哪种实现类!
分析一下源码,会发现线程池用的实现类是FutureTask
,关键核心源码如下:
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
FutureTask
类是一个实现了Future
接口所有功能的具体类,可直接使用它来实现获取异步任务执行的结果值。
FutureTask
的工作原理其实也并不复杂,它接受一个Callable
或者Runnable
对象作为参数,然后在线程池执行器中执行该任务,最后通过get()
方法可以同步等待获取任务的执行结果。
真正起到关键作用的是,在FutureTask
内部,封装了一个状态变量,用于记录任务的状态(等待、运行、完成、取消等),以及任务执行结果或异常信息,通过该状态变量,我们可以判断任务是否已完成、以及获取任务的执行结果等信息。
因为FutureTask
也实现了Runnable
接口,因此我们也可以将FutureTask
作为任务,提交给线程池执行器。
具体示例如下:
public class FutureTest {
public static void main(String[] args) throws Exception {
// 1.创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 2.初始化一个任务
Callable<String> callable = new Task();
// 3.创建FutureTask对象
FutureTask<String> futureTask = new FutureTask<>(callable);
// 4.提交任务给执行器执行
executor.execute(futureTask);
// 5.获取任务的执行结果
String result = futureTask.get(3, TimeUnit.SECONDS);
System.out.println("任务执行结果:" + result);
// 6.关闭线程池(可选)
executor.shutdown();
}
}
输出结果同上!
如果想尝试取消任务的执行,也可以通过如下方式来实现!
boolean isSuccess = futureTask.cancel(true);
System.out.println("任务是否取消成功:" + isSuccess);
除此之外,如果仔细的分析Future
接口的类关系,会发现它的实现类非常的多,FutureTask
只是它的一个基础实现类而已,部分类关系图如下!
其它常用实现类简介:
CompletableFuture
:支持传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法ForkJoinTask
:支持把一个大任务拆成多个小任务,然后并行执行,在多核 CPU 上可以显著提升程序的执行效率ScheduledFuture
:支持周期性定时的执行任务,其中ScheduledFutureTask
是一个私有类,只能通过ScheduledThreadPoolExecutor
初始化操作
关于CompletableFuture
、ForkJoinTask
和ScheduledFuture
,我们会在后面的文章中,再次单独介绍具体的用法。
三、小结
本文主要围绕Future
接口用法做了一次简单的知识总结,其中FutureTask
类是Future
接口中一个非常重要的实现类,通过它可以获取异步任务执行的返回值,通常用于异步计算带有返回值的任务。
限于篇幅的原因,本文没有对FutureTask
做过深入的原理讲解,主要围绕具体用法进行介绍,有兴趣的朋友可以阅读这篇文章《Java的Future机制详解》,以便更清晰的了解它的实现原理。
如果有描述不对的地方,欢迎留言指出,共同进步!
四、参考
1.https://www.liaoxuefeng.com/wiki/1252599548343744/1306581155184674
2.https://www.cnblogs.com/xrq730/p/4872722.html
3.https://juejin.cn/post/7231074060787908663
4.https://zhuanlan.zhihu.com/p/54459770
多线程系列(十九) -Future使用详解的更多相关文章
- SpringBoot系列(十二)过滤器配置详解
SpringBoot(十二)过滤器详解 往期精彩推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件 ...
- 十九、linux--RAID详解
一.什么是RADI Raid是廉价冗余磁盘阵列,简称磁盘阵列. 运维人员就叫RAID.Raid是一种把多块独立的磁盘(物理磁盘)按不同方式组合起来形成一个磁盘组,在逻辑上看起来就是一个大的磁盘,从而提 ...
- “全栈2019”Java多线程第二十九章:可重入锁与不可重入锁详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java多线程第十九章:死锁详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java进阶(三十二) HttpClient使用详解
Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...
- “全栈2019”Java异常第十八章:Exception详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...
- “全栈2019”Java第二十八章:数组详解(上篇)
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Java基础学习总结(33)——Java8 十大新特性详解
Java8 十大新特性详解 本教程将Java8的新特新逐一列出,并将使用简单的代码示例来指导你如何使用默认接口方法,lambda表达式,方法引用以及多重Annotation,之后你将会学到最新的API ...
- 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)
上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, ...
- Web 前端开发精华文章集锦(jQuery、HTML5、CSS3)【系列十九】
<Web 前端开发精华文章推荐>2013年第七期(总第十九期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和 C ...
随机推荐
- .NetCore 三种生命周期注入方式
.NetCore彻底诠释了"万物皆可注入"这句话的含义,在.NetCore中到处可见注入的使用.因此core中也提供了三种注入方式的使用,分别是: AddTransient:每次请 ...
- AI自动生成视频保姆级教程,还能赚包辣条哦~
友友们,小卷今天给大家分享下如何通过AI自动生成视频,只需要3分钟就能做出一个视频,把视频发到B站.抖音.西瓜上,还能赚包辣条哦~ 文末给大家准备了AI变现的案例及AIGC知识库,记得领取哦! 1.收 ...
- 内存泄漏定位工具之 mtrace(一)
1 前言 mtrace(memory trace),是 GNU Glibc 自带的内存问题检测工具,它可以用来协助定位内存泄露问题.它的实现源码在glibc源码的malloc目录下,其基本设计原理为设 ...
- 蓝鲸:安装SaaS组件bk_monitor失败分析解决
使用./bk_install saas-o 安装发现bk_monitor(蓝鲸监控)组件报错"ERROR deploy failed: timeout". 单独尝试安装各个组件: ...
- SATA 学习笔记——Frame/Primitive解析
一.故事前传 我们之前说到Link layer的结构,link layer的作用大致可以包括以下几点: Frame flow control CRC的生成与检测(已解析,详细见历史文章) 对数据与控制 ...
- 解锁Mysql中的JSON数据类型,怎一个爽字了得
引言 在实际业务开发中,随着业务的变化,数据的复杂性和多样性不断增加.传统的关系型数据库模型在这种情况下会显得受限,因为它们需要预先定义严格的数据模式,并且通常只能存储具有相同结构的数据.而面对非结构 ...
- 普冉PY32系列(十一) 基于PY32F002A的6+1通道遥控小车II - 控制篇
目录 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU简介 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境 普冉PY32系列(三) P ...
- STC8H8K64U 的 USB 功能测试(续)
对 STC8H8K64U 的USB测试昨天没搞定, 判断可能是供电的问题, 直接用5V不行, 从USB2TTL上采电3.3V时存在一个问题, 就是 D-/D+ 在上电前就已经连接了, 不符合 USB ...
- Swoole从入门到入土(24)——多进程[进程管理器Process\Manager]
Swoole提供的进程管理器Process\Manage,基于 Process\Pool 实现.可以管理多个进程.相比与 Process\Pool,可以非常方便的创建多个执行不同任务的进程,并且可以控 ...
- 如何在C#中使用 Excel 动态函数生成依赖列表
前言 在Excel 中,依赖列表或级联下拉列表表示两个或多个列表,其中一个列表的项根据另一个列表而变化.依赖列表通常用于Excel的业务报告,例如学术记分卡中的[班级-学生]列表.区域销售报告中的[区 ...