Callable、Runnable、Future 和 FutureTask

news2025/1/19 8:10:52

CallableFuture 是 Java 在后续版本中引入的,Callable 类似于 Runnable 接口,实现 Callback 接口的类与实现 Runnable 接口的类都可以用于被被线程执行的任务。

以下是两个接口的相关源码:

// /xref/libcore/ojluni/src/main/java/java/util/concurrent/Callable.java
public interface Callable<V> {
    V call() throws Exception;
}

// /xref/libcore/ojluni/src/main/java/java/lang/Runnable.java
public interface Runnable {
    public abstract void run();
}

通过以上源码可以看出:Callable.call() 方法是有返回值的,并且可以抛出异常;Runable.run() 方法没有返回值而且不会抛出异常。

Callable 接口可以看作是 Runnable 接口的补充,和 Runnable 的区别在于它会返回执行任务的结果,并且可以抛出异常,一般是配合 ThreadPoolExecutor 使用。

Future 是一个接口,它可以对 Runnable 或者 Callable 任务进行取消、判断任务是否取消、查询任务是否完成以及获取任务结果。以下是 Future 的相关源码:

public interface Future<V> {

  	// 取消任务的执行。任务已经完成或者已经被取消时调用此方法,会返回 false
    boolean cancel(boolean mayInterruptIfRunning);

  	// 判断任务是否被取消
    boolean isCancelled();

  	// 判断任务是否完成。正常完成、异常以及被取消,都将返回 true
    boolean isDone();

  	// 阻塞地获取任务的执行结果
    V get() throws InterruptedException, ExecutionException;

  	// 在一定时间内,阻塞地获取任务的执行结果
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

但是由于 Future 只是一个接口,为了使用它里面的功能,必须使用它的间接实现类 FutureTask,代表异步计算的结果。

FutureTaskRunnableFuture 接口的实现类,RunnableFuture 接口实现了 RunnableFuture 接口。因此 FutureTask 是可以交给 Thread 或者 Executor 执行的。

以下是 FutureTask 的继承关系:

FutureTask 的继承关系

根据 FutureTask.run() 方法被执行的时机,FutureTask 可以处于下面 3 种状态:

  • 未启动:FutureTask.run() 方法还没有被执行之前,就处于未启动状态;
  • 已启动:FutureTask.run() 方法在被执行过程中,FutureTask 处于已启动状态;
  • 已完成:FutureTask.run() 方法执行完成后正常结束,或者执行 FutureTask.cacel(boolean),被取消,或者执行 FutureTask.run() 方法时抛出异常而结束,就处于已完成状态;

以下是 FutureTask 的状态变化图:

FutureTask 的状态

对于 FutureTask.get()/FutureTask.get(long, TimeUnit) 方法:

  • FutureTask 处于未启动或已启动的状态时,执行 FutureTask.get()/FutureTask.get(long, TimeUnit) 方法将导致线程阻塞;
  • FutureTask 处于已完成状态时,执行 FutureTask.get()/FutureTask.get(long, TimeUnit) 方法将导致调用线程立即返回结果或者抛出异常;

对于 FutureTask.cancel(boolean) 方法:

  • FutureTask 处于未启动状态时,执行 FutureTask.cancel(boolean) 方法将导致此任务永远不会被执行;
  • FutureTask 处于已启动状态时,执行 FutureTask.cancel(true) 方法将以中断执行此任务线程的方式来试图停止任务;
  • FutureTask 处于已启动状态时,执行 FutureTask.cancel(false) 方法将不会对正在执行的任务的线程产生影响(让正在执行的任务运行完成);
  • FutureTask 处于已完成状态时,执行 FutureTask.cancel(boolean) 方法将返回 false

FutureTask 的 get 和 cancel 执行示意图

可以把 FutureTask 交给 Executor 执行,也可以通过 <T> Future<T> ExecutorService.submit(Runnable, T)/Future<?> ExecutorService.submit(Runnable) 方法返回一个 Future 对象,但是由于 Future 是一个接口,所以这里一般是返回 FutureTask 对象。之后就可以调用 FutureTask.get()/get(long, TimeUnit) 方法或者 FutureTask.cancel(boolean) 方法。

当一个线程需要等待另一个线程把某个任务执行完成后它才能继续执行,此时就可以使用 FutureTask。假如有多个线程执行若干个任务。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完成后才能继续执行。

以下是 FutureTask 的代码:

private final ConcurrentHashMap<Object, Future<String>> taskCache = new ConcurrentHashMap<>();

    private String executionTask(final String taskName) {
        while (true) {
            Future<String> future = taskCache.get(taskName); // 1.1 2.1
            if (future == null) {
                Callable<String> task = new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        return taskName;
                    }
                };
                FutureTask<String> futureTask = new FutureTask<>(task);
                future = taskCache.putIfAbsent(taskName, futureTask); // 1.3
                if (future == null) {
                    future = futureTask;
                    futureTask.run(); // 1.4 执行任务
                }
            }
            try {
                return future.get(); // 1.5 2.2 线程在此等待任务执行完成
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

以下是代码示意图:

代码执行过程

当两线程试图同时执行同一个任务时,如果 Thread 1 在执行万 1.3 后,Thread 2 执行 2.1,那么接下来 Thread 2 将在 2.2 等待,直到 Thread 1 执行完 1.4 后,Thread 2 才能从 2.2 返回。

以下是 FutureTask 的用法:

public class Test {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        Thread.sleep(100);
        futureTask.cancel(true); // 1
        System.out.println("future is cancel: " + futureTask.isCancelled());
        if (!futureTask.isCancelled()) {
            System.out.println("future is cancelled");
        }
        System.out.println("future is done: " + futureTask.isDone());
        if (!futureTask.isDone()) {
            System.out.println("future get = " + futureTask.get()); // 2
        } else {
            System.out.println("task is done");
        }
    }
}


class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "-call is running");
        Thread.sleep(5000);
        return "Hello World";
    }
}

以下是执行结果:

执行结果

在注释 1 处尝试取消任务的执行,如果该方法在任务已完成或者已取消则返回 false,如果能够取消还未完成的任务,则返回 true,因为在上面的代码中由于任务还处于休眠状态,所以可以取消任务。在注释 2 处,FutureTask.get() 方法会阻塞当前线程,知道任务执行完成返回结果为止。

对于 CallableFuture 的使用:

public class Test {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable<String> call = new MyCallable();
        Future<String> future = executorService.submit(call);
        executorService.shutdown();
        Thread.sleep(5000);
        System.out.println("主线程休眠 5 秒,当前时间: " + System.currentTimeMillis());
        String str = future.get();
        System.out.println("Future 已拿到数据,str = " + str + "; 当前时间为: " + System.currentTimeMillis());
    }
}

class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("进入 call 方法,开始休眠,休眠的时间为: " + System.currentTimeMillis());
        Thread.sleep(10000);
        return "Hello World";
    }
}

执行结果:

执行结果

在上面的代码中,future 是直接放到线程池中去执行的。从执行结果来看,主线程虽然休眠了 5s,但是从 call() 方法执行拿到任务的结果,这中间的时间差是 10s,说明 FutureTask.get() 方法会阻塞当前线程直到任务完成。

ExecutorService.shutdow() 方法,线程池就处于 SHUTDOWN 的状态,此时线程池不能够接收新的任务,它会等待所有任务执行完毕。如果调用 ExecutorService.shutdownNow() 方法,则线程处于 STOP 状态,此时线程池不能够接受新的任务,并且会去尝试终止正在执行的结果。

通过 FutureTask 也可以达到同样的效果:

public class Test {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable<String> call = new MyCallable();
        FutureTask<String> task = new FutureTask<>(call);
        executorService.submit(task);
        executorService.shutdown();
        Thread.sleep(5000);
        System.out.println("主线程休眠 5 秒,当前时间: " + System.currentTimeMillis());
        String str = task.get();
        System.out.println("Future 已拿到数据,str = " + str + "; 当前时间为: " + System.currentTimeMillis());
    }
}

class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("进入 call 方法,开始休眠,休眠的时间为: " + System.currentTimeMillis());
        Thread.sleep(10000);
        return "Hello World";
    }
}

执行结果:

执行结果

以上的组合带来了以下的变化:如果有一种场景,方法 A 返回一个数据需要 10s,A 方法后面的代码运行需要 20s,但是在 20s 的执行结果中,只有后面 10s 依赖于方法 A 执行的结果。如果与以往采用同样的方式,势必会有 10s 的时间被浪费,如果采用前面两种组合,则效率会提高:

  1. 先把 A 方法的内容放到 Callable 实现类的 call() 方法中;
  2. 在主线程中通过线程池执行 A 任务;
  3. 执行后面方法中 10s 不依赖方法 A 运行结果的代码
  4. 获取方法 A 的运行结果,执行后面方法中 10s 依赖方法 A 运行结果的代码;

这样代码的效率就提高了,程序不必卡在 A 方法处。

参考

搞懂 Runnable、Future、Callable、FutureTask 之间的关系,这篇就够了

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

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

相关文章

我为什么开发个人版的ChatGPT,而不使用集成好的商业版的十大好处。

随着人工智能技术的不断发展&#xff0c;ChatGPT已经成为了许多人工智能爱好者的热门话题。然而&#xff0c;面对现有的商业版ChatGPT&#xff0c;许多人可能会感到疑惑&#xff1a;为什么要自己开发个人版的ChatGPT&#xff0c;而不是直接使用集成好的商业版&#xff1f;本文将…

C++ 并发编程

文章目录基本概念编程创建线程启动共享数据相关条件变量时间相关future相关——等待一次性事件读写锁原子操作与缓存一致性关系线程管理启动线程从类的方法来创建线程传参标识线程常用API等待线程完成后台运行线程移动线程间共享数据互斥量&#xff08;mutex&#xff09;unique…

公网WebSocket Client远程连接本地WebSocket Server【内网穿透】

目录 1. Java 服务端demo环境 2. 在pom文件引入第三包封装的netty框架maven坐标 3. 创建服务端,以接口模式调用,方便外部调用 4. 启动服务,出现以下信息表示启动成功,暴露端口默认9999 5. 创建隧道映射内网端口 6. 创建隧道映射本地端口 7. 测试公网远程连接 1. Java 服…

一文轻松教会你基于Excel+关键字驱动的自动化测试框架封装

目录 一、概述 二、框架设计 测试用例管理 关键字库封装 数据驱动设计 日志记录和报告生成 三、框架实现 测试用例管理 关键字库封装 数据驱动设计 日志记录和报告生成 四、框架使用 编写测试用例 编写关键字库 执行测试 五、总结 一、概述 在软件开发过程中&a…

【Spring Security】| 从0到1编写一个权限认证 | 学会了吗?

目录一. &#x1f981; 认证前的工作1. 添加依赖2. 创建数据库表&#xff08;数据自行添加&#xff09;3. 编写用户实体类4. 编写Dao接口5. 在启动类中添加 MapperScan 注解6. 继续添加各种包二. &#x1f981; 自定义逻辑认证原理—UserDetailsService三. &#x1f981; 数据库…

Android Studio Flamingo | 2022.2.1 发布,快来看看有什么更新吧

原文链接 https://developer.android.com/studio/releases 新的 Android Studio 版本 Flamingo (火烈鸟) 已经发布&#xff0c;本次更改最有意思的点在于&#xff1a; Flamingo 自带的 JDK 是 JDK 17 而不再是 JDK 11&#xff0c;另外还有如 IDE 支持应用主题图标和动态颜色&am…

TikTok和国产抖音的发展路径和趋势

鑫优尚电子商务&#xff1a;以历史为镜子&#xff0c;我们可以知道变化。 纵观TikTok和国产抖音&#xff0c;我们会发现TikTok目前的发展路径和趋势与国产抖音的主线十分相似&#xff0c;直播也是如此。鑫优尚电子商务 国内抖音直播一般经历过四个时代&#xff1a;直播1.0时代…

AIGC周报|清华、北邮新研究:让文生图AI更懂你

AIGC通过借鉴现有的、人类创造的内容来快速完成内容创作。ChatGPT、Bard等AI聊天机器人以及DallE 2、Stable Diffusion等文生图模型都属于AIGC的典型案例。「AIGC技术周报」将为你带来最新的paper、博客等前瞻性研究。 OpenAGI&#xff1a;当大模型遇见领域专家 “愿原力与大型…

分子生物学 第五章 DNA损伤修复和突变

文章目录第五章 DNA损伤修复和突变第一节第二节 DNA损伤的类型1 造成DNA损伤的因素2 DNA损伤的类型3 DNA损伤修复机制3.1 直接修复3.2 切除修复3.3 双链断裂修复3.4 重组修复3.5 跨越合成第五章 DNA损伤修复和突变 第一节 损伤&#xff1a;比如碱基&#xff0c;甲基化 突变&…

JavaSE学习进阶day04_04 正则表达式和Lambda表达式

第六章 正则表达式&#xff08;超级重要&#xff09; 开发心得&#xff1a;看着正确数据&#xff0c;从左到右书写正则表达式 6.1 正则表达式的概念及演示 在Java中&#xff0c;我们经常需要验证一些字符串&#xff0c;例如&#xff1a;年龄必须是2位的数字、用户名必须是8位…

爬虫日常练习-协程方式爬取图片

文章目录前言代码设计前言 hello朋友们&#xff0c;欢迎回来。这里是无聊的网友。今天给大家分享另一种处理多任务的方法–协程 那么在开始之前我们首先要了解什么是协程。协程是在一个线程内&#xff1a;多个任务出现阻塞时&#xff0c;由envet_loop轮转查看阻塞状态&#…

Zeppelin0.9.0 连接 Hive 3.1.2(踩坑,亲测有效)

一、前提 已经安装好Hadoop、Hive&#xff08;可以启动hiveserver2&#xff09;、Zeppelin 1.启动Hadoop [roothurys24 ~]# start-all.sh 2.启动hiveserver2 [roothurys24 ~]# cd /opt/soft/hive312/conf/ [roothurys24 conf]# nohup ../bin/hive --service hiveserver2 &a…

kubernetes之Ingress介绍

Ingress 组成 ingress controller将新加入的Ingress转化成Nginx的配置文件并使之生效 ingress服务将Nginx的配置抽象成一个Ingress对象&#xff0c;每添加一个新的服务只需写一个新的Ingress的yaml文件即可工作原理 1.ingress controller通过和kubernetes api交互&#xff0…

【机器学习(五)】基于KNN模型对高炉发电量进行回归预测分析

文章目录专栏导读1、KNN简介2、KNN回归模型介绍3、KNN模型应用-高炉发电量预测3.1数据集信息&#xff1a;3.2属性信息3.3数据准备3.4数据标准化和划分数据集3.5寻找最佳K值3.6建立KNN模型预测4、完整代码专栏导读 ✍ 作者简介&#xff1a;i阿极&#xff0c;CSDN Python领域新星…

利用74373芯片进行单片机IO口扩展的方法介绍-成都控制设备订做

本文介绍用74373芯片进行微处理器IO口扩展的方法。 1.为什么要进行IO口扩展&#xff1f; 在电路设计的某些时候&#xff0c;微处理器&#xff08;如单片机&#xff09;IO口不够用了&#xff0c;此时该怎么办呢&#xff1f;利用辅助芯片进行IO口扩展是个简单直接的方法&#xff…

开源社与 Dev.Together 2022

思否与开源社携手11>2在 2023 年的春天&#xff0c;开源社走进了 Dev.Together 2022 的会场&#xff0c;一时间有种时空错觉。2022 年本该举办的开源聚会因为不可抗力的因素被延期&#xff0c;感谢思否一直坚持的理念&#xff1a;Dev.Together Summit 只做线下&#xff0c;将…

debian 10 安装神州通用数据库 V7.0

debian 10 安装神州通用数据库 V7.01、官方下载链接2、windows客户端下载链接3、官方安装手册4、安装前准备3.1、创建安装用户3.2、以root 用户修改神通数据库安装包的所有者为shentong 用户3.3、以root 用户创建神通数据库主目录并修改所有者为shentong 用户3.4、以root 用户临…

c/c++:顺序结构,if else分支语句,do while循环语句,switch case break语句

c/c:顺序结构&#xff0c;if else分支语句&#xff0c;do while循环语句&#xff0c;switch case break语句 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;此时学会c的话&#xff0c; 我所知道的周边的会c的同学&#xff…

一文读懂域名注册

本文深入浅出讲解域名的注册、建站和管理&#xff0c;通过文章可以了解以下问题&#xff1a; 域名注册及建站流程&#xff1b;域名注册的技术原理&#xff1b;域名管理&#xff08;修改 DNS 服务器、转入转出、自定义 DNSHost、whois 信息&#xff09;。 众所周知&#xff0c;…

【MYSQL】表的增删改查(基础)

文章目录&#x1f337; 1. 新增&#xff08;Create&#xff09;⭐️ 1.1 单行行数据 指定列插入⭐️ 1.2 多行数据 指定列插入&#x1f337; 2. 查询&#xff08;Retrieve&#xff09;⭐️ 2.1 全列查询⭐️ 2.2 指定列查询⭐️ 2.3 查询字段为表达式⭐️ 2.4 别名⭐️ 2.5 去…