Java 并发编程面试题——Future

news2024/10/6 8:32:52

目录

  • 1.什么是 Future 模式?Java 中是如何实现的?
  • 2.Callable、Future 与 FutureTask 分别是什么?
    • 2.1.Callable 接口
    • 2.2.Future 接口
    • 2.3.FutureTask 类
  • 3.CompletableFuture 类有什么用?

1.什么是 Future 模式?Java 中是如何实现的?

(1)Future 模式是一种并发编程模式,它允许异步执行代码并在未来获取其结果。在 Future 模式中,调用线程可以提交一个任务给另一个线程或线程池,并立即返回一个 Future 对象作为任务的代理。Future 对象表示了尚未完成的任务,并允许调用线程在未来的某个时刻获取任务的结果。Future 模式通常用于处理长时间运行的任务,例如网络请求或耗时的计算。通过使用 Future 模式,调用线程可以避免阻塞并继续执行其他任务,同时仍然能够获得任务的结果。

(2)在 Java 中,Future 模式是通过 Future 接口来实现的。Java 还提供了 CompletableFuture 类,它是 Future 接口的实现,并提供了更丰富的功能,例如异步回调异常处理。Java 设计到的相关接口和类如下图所示:

在这里插入图片描述

2.Callable、Future 与 FutureTask 分别是什么?

通常来说,我们使用 Runnable 和 Thread 来创建一个新的线程。但是它们有一个弊端,就是 run() 是没有返回值的。而有时候我们希望开启一个线程去执行一个任务,并且这个任务执行完成后有一个返回值。JDK 提供了 Callable 接口与 Future 类为我们解决这个问题,这也是所谓的“异步”模型。

2.1.Callable 接口

(1)Callable 与 Runnable 类似,同样是只有⼀个抽象方法的函数式接看。不同的是, Callable 提供的方法是有返回值的,而且支持泛型。Callable 接口的特点如下:

  • 为了实现 Runnable,需要实现不返回任何内容的 run() 方法,而对于Callable,需要实现在完成时返回结果的 call() 方法;
  • call() 方法可以引发异常,而 run() 则不能;
  • 为实现 Callable 而必须重写 call() 方法;
  • 不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable;
@FunctionalInterface
public interface Callable<V> {
	V call() throws Exception; 
}
class MyThread1 implements Runnable {
    @Override
    public void run() {
        //无返回值
    }
}

class MyThread2 implements Callable {
    @Override
    public Object call() throws Exception {
        return 1;
    }
}

(2)那⼀般是怎么使用 Callable 的呢? Callable⼀般配合线程池工具 ExecutorService 来使用。这里只介绍 ExecutorService 可以使用 submit 方法来让⼀个 Callable 接口执行。它会返回⼀个 Future ,我们后续的程序可以通过这个 Future 的 get 方法得到结果。这里可以看⼀个简单的使用案例:

import java.util.concurrent.*;

class Task implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 模拟计算需要⼀秒
        Thread.sleep(1000);
        return 2;
    }
    
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        // 使⽤
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        // ExecutorService.submit() 方法返回的其实就是 Future 的实现类 FutureTask
        Future<Integer> result = executor.submit(task);
        //注意调⽤ get ⽅法会阻塞当前线程,直到得到结果,所以实际编码中建议使⽤可以设置超时时间的重载 get ⽅法
        System.out.println(result.get());
    }
}

输出结果:

2

2.2.Future 接口

(1)在 Java 中,Future 类是一个泛型接口,位于 java.util.concurrent 包下,其包含的方法如下:

package java.util.concurrent;

// V 表示任务返回值的类型
public interface Future<V> {

    //成功取消任务返回 true,否则返回 false
    boolean cancel(boolean mayInterruptIfRunning);

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

    //判断任务是否已经执行完成
    boolean isDone();

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

    //指定时间内没有返回计算结果就抛出 TimeOutException 异常
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

简单理解 Future:现在有一个任务,提交给了 Future 来处理。任务执行期间我自己可以去做任何想做的事情。并且,在这期间我还可以取消任务以及获取任务的执行状态。一段时间之后,我就可以 Future 那里直接取出任务执行结果。

(2)cancel 方法是试图取消⼀个线程的执行。 注意是试图取消,并不⼀定能取消成功。因为任务可能已完成、已取消、或者⼀些其它因素不能取消,存在取消失败的可能。boolean 类型的返回值是“是否取消成功”的意思。参数 paramBoolean 表示是否采用中断的方式取消线程执行。 所以有时候为了让任务有能够取消的功能,就使用 Callable 来代替 Runnable 。 如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。

2.3.FutureTask 类

(1)上面介绍了 Future 接口。这个接口有⼀个实现类叫 FutureTask 。 FutureTask 是实现的 RunnableFuture 接口的,而 RunnableFuture 接口同时继承了 Runnable 接口和 Future 接口

public interface RunnableFuture<V> extends Runnable, Future<V> {
	/**
	* Sets this Future to the result of its computation
	* unless it has been cancelled.
	*/
	void run(); 
}

(2)那 FutureTask 类有什么用?前面说到了 Future 只是⼀个接口,而它里面的 cancel、get、isDone 等方法要自己实现起来都是非常复杂的。所以 JDK 提供了⼀个 FutureTask 类来供我们使用。FutureTask 有两个构造函数,可传入 Callable 或者 Runnable 对象。实际上,传入 Runnable 对象也会在方法内部转换为 Callable 对象

public class FutureTask<V> implements RunnableFuture<V> {
	
	//...
	
	public FutureTask(Callable<V> callable) {
	    if (callable == null)
	        throw new NullPointerException();
	    this.callable = callable;
	    this.state = NEW;
	}
	
	public FutureTask(Runnable runnable, V result) {
	    // 通过适配器 RunnableAdapter 来将 Runnable 对象 runnable 转换成 Callable 对象
	    this.callable = Executors.callable(runnable, result);
	    this.state = NEW;
	}
}

FutureTask 相当于对 Callable 进行了封装,管理着任务执行的情况,存储了 Callable 的 call 方法的任务执行结果。

(3)示例代码如下:

import java.util.concurrent.*;

//自定义 Callable,与上面⼀样
class Task implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //模拟计算需要⼀秒
        Thread.sleep(1000);
        return 2;
    }
    
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

使用上与第⼀个 Demo 有⼀点小的区别:

  • 此处调用 submit 方法是没有返回值的,因为这里实际上是调用的 submit(Runnable task) 方法,而上面的 Demo,调用的是 submit(Callable task) 方法。
  • 这里是使用 FutureTask 的 get 方法来获取返回值,而上面的 Demo 是通过 submit 方法返回的 Future 去取值。 在很多高并发的环境下,有可能 Callable 和 FutureTask 会创建多次。FutureTask 能够在高并发环境下确保任务只执行⼀次。这块可以查看 FutureTask 源码。

(4)核心原理

  • 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成;
  • 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态;
  • 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果;
  • 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get() 方法,一旦计算完成,就不能再重新开始或取消计算
  • get() 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常;
  • get() 只计算一次,因此 get() 方法放到最后。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//比较Runnable 和 Callable 这两个接口

class MyThread1 implements Runnable {
    @Override
    public void run() {
        //无返回值
    }
}

class MyThread2 implements Callable {
    @Override
    public Object call() throws Exception {
        return 1;
    }
}

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //使用 Runnable 创建线程
        new Thread(new MyThread1(), "AA").start();
    
        /*
        	使用Callable创建线程
            不能像上面那样直接创建 new Thread(new MyThread2(), "BB").start();
        */
        //FutureTask
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
        
        //使用 lambda 表达式进行简化
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName() + " enters the callable .");
            return 1;
        });
        
        //创建一个线程
        new Thread(futureTask2, "Luck").start();
        while (!futureTask2.isDone()) {
            System.out.println("wait...");
        }
        //调用 FutureTask 的get()
        System.out.println(futureTask2.get());
        //只进行一次计算
        System.out.println(futureTask2.get());
        System.out.println(Thread.currentThread().getName() + " is over !");
    }
}

(5)FutureTask 的几种状态

/**
 * state 可能的状态转变路径如下:
 * NEW -> COMPLETING -> NORMAL
 * NEW -> COMPLETING -> EXCEPTIONAL
 * NEW -> CANCELLED
 * NEW -> INTERRUPTING -> INTERRUPTED
 */
private volatile int state;
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;

state 表示任务的运行状态,初始状态为 NEW。运行状态只会在 set、setException、cancel 方法中终止。COMPLETING、INTERRUPTING 是任务完成后的瞬时状态。

3.CompletableFuture 类有什么用?

(1)Future 在实际使用过程中存在一些局限性,例如不支持异步任务的编排组合、获取计算结果的 get() 方法为阻塞调用等。Java 8 才被引入 CompletableFuture 类可以解决 Future 的这些缺陷。CompletableFuture 除了提供了更为好用和强大的 Future 特性之外,还提供了函数式编程、异步任务编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)等能力。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
	//...
}

(2)可以看到,CompletableFuture 同时实现了 Future 接口CompletionStage 接口。其中,CompletionStage 接口描述了一个异步计算的阶段。很多计算可以分成多个阶段或步骤,此时可以通过它将所有步骤组合起来,形成异步计算的流水线。CompletionStage 接口中的方法比较多,CompletableFuture 的函数式能力就是这个接口赋予的。从这个接口的方法参数可以发现其大量使用了 Java 8 引入的函数式编程

在这里插入图片描述

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

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

相关文章

windows系统管理_Windows server 2016 组管理与授权

组账户的概述 在 windows 服务器中&#xff0c;当我们需要为多个用户设置相同的权限时&#xff0c;一个一个的逐一设置会比较 麻烦&#xff0c;这个时候我们就需要用到另一种模式&#xff0c;组账户&#xff0c;使用此账户来进行简化操作。 在以后的职场中&#xff0c;每家公司…

Flink 优化 (五) --------- Job 优化

目录一、使用 DataGen 造数据1. DataStream 的 DataGenerator2. SQL 的 DataGenerator二、算子指定 UUID三、链路延迟测量四、开启对象重用五、细粒度滑动窗口优化一、使用 DataGen 造数据 开发完 Flink 作业&#xff0c;压测的方式很简单&#xff0c;先在 kafka 中积压数据&a…

全景图像畸变校正

1.简介 理想的相机基本上是小孔成像的&#xff0c;在小孔成像模型中&#xff0c;如果焦距一定&#xff0c;那么图像传感器像素平面的面积直接决定了相机视场角的大小&#xff0c;超过这个视场角范围的物体不会被镜头获取到。因此基于透镜成像原理的相机&#xff0c;视场角无法…

JAVA解析XML时的内存消耗问题

问题出现 最近一个项目中&#xff0c;有个需求功能是从外部传入的XML读取数据&#xff0c;然后写入到数据库中。 写完之后&#xff0c;有在本地电脑上的Tomcat跑了一下&#xff0c;正常读取到XML中的数据&#xff0c;并整理好后&#xff0c;插入了数据库保存了。但是线上运行的…

C语言文件操作复习回顾(2)

TIPS 文件的顺序读写&#xff1a;fgetc, fputc, fputs&#xff08;一行字符串的输出\n注意一下&#xff09;, fgets&#xff08;一行字符串的输入\n三特性&#xff09;&#xff0c;fprintf&#xff08;格式化字符串的输出联想printf很简单&#xff09;&#xff0c;fscanf&…

pyspark 实验二,rdd编程

1.环境准备 start-all.sh启动Hadoop ./bin start-all.sh 启动spark 上传数据集 1.求该系总共多少学生 linessc.textFile("file:///home/data.txt") res lines.map(lambda x:x.split(",")).map(lambda x:x[0]) sumres.distinct() sum.cont() 2.求该系设置…

【MybatisPlus快速入门】—— 进阶入门

进阶篇 1.1 映射专题 Mybatis 框架之所以能够简化数据库操作&#xff0c;是因为他内部的映射机制&#xff0c;通过自动映射&#xff0c;进行数据的封装&#xff0c;我们只要符合映射规则&#xff0c;就可以快速高效的完成SQL操作的实现既然 MybatisPlus 是基于Mybatis 的增强…

程序员如何能提高自己的编程水平?

这些实用的小建议&#xff0c;能帮你迅速地提高编程水平&#xff1a; 不要做无意义的奋斗 拒绝喊口号和无意义的奋斗&#xff0c;包括但不限于&#xff1a; ①做了计划表却从未有执行的一天&#xff1b; ②每天都是最早来、最晚走&#xff0c;但是工作进度趋近于0&#xff1b…

ASP.NET Core MVC 从入门到精通之接化发(一)

随着技术的发展&#xff0c;ASP.NET Core MVC也推出了好长时间&#xff0c;经过不断的版本更新迭代&#xff0c;已经越来越完善&#xff0c;本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容&#xff0c;适用于初学者&#xff0c;在校毕业生&#xff0c…

PathCore:IAD文献解读

论文链接&#xff1a;[Towards Total Recall in Industrial Anomaly Detection]Towards Total Recall in Industrial Anomaly Detection &#xff1a;数据集&#xff0c; &#xff1a;标签 : 在ImageNet上预训练后的网络 第 张图 网络中第 层 1. Locall…

Sentinel学习笔记

Sentinel 官方文档&#xff1a; https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5 SpringCloud Alibaba&#xff1a; https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel 是什么…

丝滑的打包部署,一套带走~

以下文章来源于悟空聊架构 &#xff0c;作者悟空聊架构 本文主要内容如下&#xff1a; 目录 一、背景 Docker打包部署方案 项目背景&#xff1a;新项目的后端框架是刚起步&#xff0c;搭建的是一套微服务框架&#xff0c;基础服务有网关 Gateway&#xff0c; Nacos 注册中心…

为什么stm32gpio引脚的翻转速度最大只有18Mhz

(1). GPIO 引脚速度&#xff1a;GPIO_Speed_2MHz (10MHz, 50MHz) ; 又称输出驱动电路的响应速度&#xff1a;&#xff08;芯片内部在I/O口的输出部分安排了多个响应速度不同的输出驱动电路&#xff0c;用户可以根据自己的需要选择合适的驱动电路&#xff0c;通过选择速度来选择…

史上最全测试开发工具视频教程详解(含自动化、性能、接口、抓包)

目录 一、UI自动化测试工具 1. uiautomator2 2. Appium 3. ATX-Test 4. Airtest 5. ATXServer2 6. STF 7. Appetizer 二、APP稳定性测试工具 8. UICrawler 9. Maxim 10. AppCrawler 三、APP性能测试工具 11. SoloPi 12. GT 四、抓包工具 13. AnyProxy …

pytorch安装教程(二)

一直用的pytorch1.2&#xff0c;有点老了&#xff0c;想换个新版本&#xff0c;换成了pytorch2.0. torch安装 安装过程最重要的就是cuda、cudnn的版本和pytorch对应。 因为要在GPU上跑代码。 删除老旧torch 我用的软件是anaconda&#xff0c;因为可以创建虚拟环境。 步骤&…

LAZADA将缩短履约时效,卖家发货倍感压力

Lazada的跨境卖家们&#xff0c;恐怕又要头疼了。 近日&#xff0c; Lazada官方宣布&#xff0c;为了提升消费者体验&#xff0c;平台将调整商家履约订单时效。从2023年5月4日起生成的订单履约时效将有所更新。 具体而言&#xff0c;内地、香港和Laz Go Global的履约节点为“点…

Qt Quick - MessageDialog 消息提示框

MessageDialog 使用总结一、概述二、使用1. 例子一2. 例子二三、常用属性一、概述 MessageDialog 最基本的用例是弹出警告框。它还允许用户根据启用的按钮以各种方式进行响应。对话框最初是不可见的。你需要首先按需设置属性&#xff0c;然后将visible设置为true或调用open()。…

FIFO设计笔记(双口RAM、同步FIFO、异步FIFO)Verilog及仿真

文章目录0、前言0.1、FIFO0.2、FIFO与RAM1、异步双口RAM1.1、原理1.2、Verilog代码1.3、tb仿真2、FIFO设计前瞻知识2.1、格雷码2.1.1、二进制转格雷码Verilog代码tb仿真2.1.2、格雷码转二进制Verilog代码tb仿真2.2、独热码3、同步FIFO3.1、同步FIFO设计原理3.1.1、设计原理3.1.…

SpringBoot中操作Redis通过所有可能的key查询存在的key并解析为对象实体的通用方法

场景 SpringBoot中操作Redis的特殊操作-批量查询(通过key的集合批量查杜绝模糊搜索)、查询并解析对象list&#xff1a; SpringBoot中操作Redis的特殊操作-批量查询(通过key的集合批量查杜绝模糊搜索)、查询并解析对象list_霸道流氓气质的博客-CSDN博客 在上面讲操作redis中特…

【未来已来】人人都说GPT,人人都怕GPT,人人都用GPT

文章目录前言一、GPT是什么&#xff1f;二、当GPT和AI遇到摄影总结前言 ChatGPT是由美国OpenAI研发的能够通过自然语言驱动的人工智能技术工具&#xff0c;因为它强大的执行力和任务处理能力&#xff0c;一经亮相就引起了极大的关注。与之类似&#xff0c;在图像智能生成方面&…