首页 > 其他分享 >多线程系列(十九) -Future使用详解

多线程系列(十九) -Future使用详解

时间:2024-03-14 11:00:57浏览次数:21  
标签:异步 多线程 接口 任务 详解 线程 执行 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

从以上的示例可以清晰的看到,当需要获取异步线程的执行结果返回值时,通常需要搭配使用FutureCallable接口来实现,大体可以用如下步骤来概括:

  • 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初始化操作

关于CompletableFutureForkJoinTaskScheduledFuture,我们会在后面的文章中,再次单独介绍具体的用法。

三、小结

本文主要围绕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
From: https://www.cnblogs.com/dxflqm/p/18072381

相关文章

  • 详解正向代理与反向代理
    详解正向代理与反向代理:https://blog.csdn.net/Dax1_/article/details/124652162?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171038229316800182182713%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171038229316800182182713&b......
  • Unity3D 基于ECS的AOI设计与实现详解
    EntityComponentSystem(ECS)是Unity3D引擎中的一种编程模式,它将游戏对象分解为实体(Entity)和组件(Component),以提高游戏性能和可维护性。对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发的技术大佬,欢迎你来交流学习。在游戏开发中,Are......
  • Unity3D 客户端网络角色的操作与行为分离设计详解
    在网络游戏中,客户端网络角色的操作与行为分离设计是非常重要的,它可以提高游戏的可维护性和扩展性,同时也可以提升游戏的性能和用户体验。对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发的技术大佬,欢迎你来交流学习。在本文中,我们......
  • HDFSRPC协议详解
    本文主要阐述HDFSRPCserver端一个socket连接接收字节流的构成,帮助读者理解HDFSRPC协议。注意hadoop版本为3.1.1。写在前面关于proto写入和读取,使用writeDelimitedTo和read,应该是通用的方式,不作过多的介绍。处理rpc各种情况以后server都会使用统一的应答格式(包含错误与正确),......
  • 常用负载均衡详解(图文总结)
    1介绍在互联网场景下,负载均衡(LoadBalance)是分布式系统架构设计中必须考虑的一个环节,它通常是指将负载流量(工作任务、访问请求)平衡、分摊到多个操作单元(服务器、组件)上去执行的过程。目的在于提供负载配比,解决性能、单点故障(高可用)和扩展性(水平伸缩)等问题。以上图为例,随着互......
  • 全网最最最详细的virtualenv的使用命令详解
    使用virtualenv可以创建一个隔离的Python环境。以下是virtualenv的一些基本和详细使用命令,帮助更好地理解和使用这个工具:安装virtualenv首先,确保你已经安装了virtualenv。如果还没有安装,可以使用下面的命令安装:pipinstallvirtualenv创建虚拟环境要创建一个新的虚拟......
  • DNS域名解析过程详解
    一、DNS系统域名系统(DomainNameSystem),是因特网使用的命名系统,用来把人们方便记忆的主机名转换为机器方便处理的IP地址。DNS协议属于应用层协议,一般是运行在UDP协议之上,使用53端口。二、域名因特网采用层次树状结构的命名方法。采用这种命名方法,任何一个连接到因特网的主机......
  • 【Python从入门到精通】函数详解
     【上图来源于网络图片】WhydoPythonprogrammerspreferdarkmode? Becauselightattractsbugs.Python的简洁性和易读性,认为这是吸引Python程序员的原因。【Python从入门到精通】专栏课程:1、【Python从入门到精通】认识Python2、【Python从入门到精通】变量&......
  • Redis 八种常用数据类型详解
    夯实基础,这篇文章带着大家回顾一下Redis中的8种常用数据类型:5种基础数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。3种特殊数据类型:HyperLogLog(基数统计)、Bitmap(位图)、Geospatial(地理位置)。Redis5种基本数据类型Redis共有5种基本数据类......
  • 详解c++多态中的析构与构造函数
    首先简单介绍一下多态。多态是面向对象编程中的概念,它允许我们使用基类类型的指针或引用来调用派生类对象的方法。C++中实现多态主要依靠虚函数和动态绑定。那怎么使用多态呢?基类指针或引用指向派生类对象。在我学习过程中,这些概念耳熟能详,但是为什么要有多态呢,先看下面这......