Day842.Future -Java 并发编程实战

news2024/10/6 10:31:56

Future

Hi,我是阿昌,今天学习记录的是关于Future的内容。

ThreadPoolExecutor 的 void execute(Runnable command) 方法,利用这个方法虽然可以提交任务,但是却没有办法获取任务的执行结果(execute() 方法没有返回值)。
很多场景下,又都是需要获取任务的执行结果的。

那 ThreadPoolExecutor 是否提供了相关功能呢?

必须的,这么重要的功能当然需要提供了。


一、如何获取任务执行结果

Java 通过 ThreadPoolExecutor 提供的 3 个 submit() 方法和 1 个 FutureTask 工具类来支持获得任务执行结果的需求。

下面先来介绍这 3 个 submit() 方法,这 3 个方法的方法签名如下。

// 提交Runnable任务
Future<?> 
  submit(Runnable task);
// 提交Callable任务
<T> Future<T> 
  submit(Callable<T> task);
// 提交Runnable任务及结果引用  
<T> Future<T> 
  submit(Runnable task, T result);

会发现它们的返回值都是 Future 接口,Future 接口有 5 个方法,都列在下面了,它们分别是取消任务的方法 cancel()、判断任务是否已取消的方法 isCancelled()、判断任务是否已结束的方法 isDone()以及2 个获得任务执行结果的 get() 和 get(timeout, unit),其中最后一个 get(timeout, unit) 支持超时机制。

通过 Future 接口的这 5 个方法会发现,提交的任务不但能够获取任务执行结果,还可以取消任务。

不过需要注意的是:这两个 get() 方法都是阻塞式的,如果被调用的时候,任务还没有执行完,那么调用 get() 方法的线程会阻塞,直到任务执行完才会被唤醒。


// 取消任务
boolean cancel(
  boolean mayInterruptIfRunning);
// 判断任务是否已取消  
boolean isCancelled();
// 判断任务是否已结束
boolean isDone();
// 获得任务执行结果
get();
// 获得任务执行结果,支持超时
get(long timeout, TimeUnit unit);

这 3 个 submit() 方法之间的区别在于方法参数不同,下面简要介绍一下。

  1. 提交 Runnable 任务 submit(Runnable task) :这个方法的参数是一个 Runnable 接口,Runnable 接口的 run() 方法是没有返回值的,所以 submit(Runnable task) 这个方法返回的 Future 仅可以用来断言任务已经结束了,类似于 Thread.join()。
  2. 提交 Callable 任务 submit(Callable task):这个方法的参数是一个 Callable 接口,它只有一个 call() 方法,并且这个方法是有返回值的,所以这个方法返回的 Future 对象可以通过调用其 get() 方法来获取任务的执行结果。
  3. 提交 Runnable 任务及结果引用 submit(Runnable task, T result):这个方法很有意思,假设这个方法返回的 Future 对象是 f,f.get() 的返回值就是传给 submit() 方法的参数 result。这个方法该怎么用呢?下面这段示例代码展示了它的经典用法。需要你注意的是 Runnable 接口的实现类 Task 声明了一个有参构造函数 Task(Result r) ,创建 Task 对象的时候传入了 result 对象,这样就能在类 Task 的 run() 方法中对 result 进行各种操作了。result 相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据。
ExecutorService executor 
  = Executors.newFixedThreadPool(1);
// 创建Result对象r
Result r = new Result();
r.setAAA(a);
// 提交任务
Future<Result> future = 
  executor.submit(new Task(r), r);  
Result fr = future.get();
// 下面等式成立
fr === r;
fr.getAAA() === a;
fr.getXXX() === x

class Task implements Runnable{
  Result r;
  //通过构造函数传入result
  Task(Result r){
    this.r = r;
  }
  void run() {
    //可以操作result
    a = r.getAAA();
    r.setXXX(x);
  }
}

下面再来介绍 FutureTask 工具类

前面提到的 Future 是一个接口,而 FutureTask 是一个实实在在的工具类,这个工具类有两个构造函数,它们的参数和前面介绍的 submit() 方法类似,所以这里我就不再赘述了。


FutureTask(Callable<V> callable);
FutureTask(Runnable runnable, V result);

那如何使用 FutureTask 呢?

其实很简单,FutureTask 实现了 Runnable 和 Future 接口,由于实现了 Runnable 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 Thread 执行;

又因为实现了 Future 接口,所以也能用来获得任务的执行结果。

下面的示例代码是将 FutureTask 对象提交给 ThreadPoolExecutor 去执行。

// 创建FutureTask
FutureTask<Integer> futureTask
  = new FutureTask<>(()-> 1+2);
// 创建线程池
ExecutorService es = 
  Executors.newCachedThreadPool();
// 提交FutureTask 
es.submit(futureTask);
// 获取计算结果
Integer result = futureTask.get();

FutureTask 对象直接被 Thread 执行的示例代码如下所示。

相信已经发现了,利用 FutureTask 对象可以很容易获取子线程的执行结果。


// 创建FutureTask
FutureTask<Integer> futureTask
  = new FutureTask<>(()-> 1+2);
// 创建并启动线程
Thread T1 = new Thread(futureTask);
T1.start();
// 获取计算结果
Integer result = futureTask.get();

二、实现最优的“烧水泡茶”程序

初中语文课文里有一篇著名数学家华罗庚先生的文章《统筹方法》,这篇文章里介绍了一个烧水泡茶的例子,文中提到最优的工序应该是下面这样:

在这里插入图片描述

下面用程序来模拟一下这个最优工序。

并发编程可以总结为三个核心问题:

  • 分工
  • 同步
  • 互斥

编写并发程序,首先要做的就是分工,所谓分工指的是如何高效地拆解任务并分配给线程。对于烧水泡茶这个程序,一种最优的分工方案可以是下图所示的这样:

用两个线程 T1 和 T2 来完成烧水泡茶程序,T1 负责洗水壶、烧开水、泡茶这三道工序,T2 负责洗茶壶、洗茶杯、拿茶叶三道工序,其中 T1 在执行泡茶这道工序时需要等待 T2 完成拿茶叶的工序。

对于 T1 的这个等待动作,应该可以想出很多种办法,例如 Thread.join()、CountDownLatch,甚至阻塞队列都可以解决,不过今天我们用 Future 特性来实现。

在这里插入图片描述
下面的示例代码就是用这一章提到的 Future 特性来实现的。

首先,创建了两个 FutureTask——ft1 和 ft2,ft1 完成洗水壶、烧开水、泡茶的任务,ft2 完成洗茶壶、洗茶杯、拿茶叶的任务;

这里需要注意的是 ft1 这个任务在执行泡茶任务前,需要等待 ft2 把茶叶拿来,所以 ft1 内部需要引用 ft2,并在执行泡茶之前,调用 ft2 的 get() 方法实现等待。


// 创建任务T2的FutureTask
FutureTask<String> ft2
  = new FutureTask<>(new T2Task());
// 创建任务T1的FutureTask
FutureTask<String> ft1
  = new FutureTask<>(new T1Task(ft2));
// 线程T1执行任务ft1
Thread T1 = new Thread(ft1);
T1.start();
// 线程T2执行任务ft2
Thread T2 = new Thread(ft2);
T2.start();
// 等待线程T1执行结果
System.out.println(ft1.get());

// T1Task需要执行的任务:
// 洗水壶、烧开水、泡茶
class T1Task implements Callable<String>{
  FutureTask<String> ft2;
  // T1任务需要T2任务的FutureTask
  T1Task(FutureTask<String> ft2){
    this.ft2 = ft2;
  }
  @Override
  String call() throws Exception {
    System.out.println("T1:洗水壶...");
    TimeUnit.SECONDS.sleep(1);
    
    System.out.println("T1:烧开水...");
    TimeUnit.SECONDS.sleep(15);
    // 获取T2线程的茶叶  
    String tf = ft2.get();
    System.out.println("T1:拿到茶叶:"+tf);

    System.out.println("T1:泡茶...");
    return "上茶:" + tf;
  }
}
// T2Task需要执行的任务:
// 洗茶壶、洗茶杯、拿茶叶
class T2Task implements Callable<String> {
  @Override
  String call() throws Exception {
    System.out.println("T2:洗茶壶...");
    TimeUnit.SECONDS.sleep(1);

    System.out.println("T2:洗茶杯...");
    TimeUnit.SECONDS.sleep(2);

    System.out.println("T2:拿茶叶...");
    TimeUnit.SECONDS.sleep(1);
    return "龙井";
  }
}
// 一次执行结果:
T1:洗水壶...
T2:洗茶壶...
T1:烧开水...
T2:洗茶杯...
T2:拿茶叶...
T1:拿到茶叶:龙井
T1:泡茶...
上茶:龙井

三、总结

利用 Java 并发包提供的 Future 可以很容易获得异步任务的执行结果,无论异步任务是通过线程池 ThreadPoolExecutor 执行的,还是通过手工创建子线程来执行的。

Future 可以类比为现实世界里的提货单,比如去蛋糕店订生日蛋糕,蛋糕店都是先给你一张提货单,你拿到提货单之后,没有必要一直在店里等着,可以先去干点其他事,比如看场电影;等看完电影后,基本上蛋糕也做好了,然后你就可以凭提货单领蛋糕了。

利用多线程可以快速将一些串行的任务并行化,从而提高性能;
如果任务之间有依赖关系,比如当前任务依赖前一个任务的执行结果,这种问题基本上都可以用 Future 来解决。

在分析这种问题的过程中,建议用有向图描述一下任务之间的依赖关系,同时将线程的分工也做好,类似于烧水泡茶最优分工方案那幅图。

对照图来写代码,好处是更形象,且不易出错。


不久前听说小明要做一个询价应用,这个应用需要从三个电商询价,然后保存在自己的数据库里。核心示例代码如下所示,由于是串行的,所以性能很慢,你来试着优化一下吧。


// 向电商S1询价,并保存
r1 = getPriceByS1();
save(r1);
// 向电商S2询价,并保存
r2 = getPriceByS2();
save(r2);
// 向电商S3询价,并保存
r3 = getPriceByS3();
save(r3);

可以用 Future

ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(3);
Future<R> future1 = threadPoolExecutor.submit(Test::getPriceByS1);
Future<R> future2 = threadPoolExecutor.submit(Test::getPriceByS2);
Future<R> future3 = threadPoolExecutor.submit(Test::getPriceByS3);
R r1 = future1.get();
R r2 = future2.get();
R r3 = future3.get();

也可以用 CompletableFuture

CompletableFuture<R> completableFuture1 = CompletableFuture.supplyAsync(Test::getPriceByS1);
CompletableFuture<R> completableFuture2 = CompletableFuture.supplyAsync(Test::getPriceByS2);
CompletableFuture<R> completableFuture3 = CompletableFuture.supplyAsync(Test::getPriceByS3);
CompletableFuture.allOf(completableFuture1, completableFuture2, completableFuture3).thenAccept(System.out::println);

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

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

相关文章

Java Web高级面试题(二)

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java面试题…

总体分布、样本分布、抽样分布的区别

总体分布、样本分布、抽样分布的区别 参考自&#xff1a;THREE DISTRIBUTIONS 1.总体分布 The population is the whole set of values, or individuals, you are interested in. The population which consists of a set of scores (5, 6, 7, 8) which distribute around a …

比Hive还快10倍的大数据计算引擎

最近几年&#xff0c;Presto这个大数据组件越来越多地出现在程序员的岗位需求中&#xff0c;很多应届同学一番自我检查后发现&#xff0c;在学校都没怎么接触过&#xff0c;更不用说了解了。 某游戏公司岗位需求 Presto到底是个啥&#xff1f; 有什么用&#xff1f; 适合哪些…

月薪9K!前台测试男生偷偷努力,工资翻倍转行5G网络优化工程师,“卷死”所有人!

你是否有过这样的生活&#xff1f; 每天重复着外场测试&#xff0c;抱着电脑&#xff0c;手机在户外验证基站信号;长期的出差&#xff0c;频繁的更换城市;每个月领着3500块钱的工资&#xff0c;可能再工作2-3年会涨到4000元&#xff0c;技术也不可能有任何的突破。生活重复单调…

Ubuntu 上安装conan + Cmake

目录 1. 在用户目录下创建共享目录&#xff0c;然后将共享目录挂载到上去 2. 安装新版本的cmake 3. 安装conan 4. 配置CONAN环境 5. 配置conan的访问服务器URL 6. Linux 平台需要切换gcc编译器版本为c11以及version 7. 安装opengl开发库 1. 在用户目录下创建共享目录&…

springboot+mybatis如何快速插入大量数据

在公司业务开发过程中&#xff0c;我们经常会遇到往数据库表中插入大量数据的场景&#xff0c;比如excel批量导入数据。那么该如何快速地插入数据呢&#xff1f;我们可以考虑使用批量插入来实现&#xff0c;该方案实测每秒能达到35000条&#xff0c;后附具体实现代码。接下来我…

经典蓝牙连接过程

ACL是连接基础&#xff0c;sco需要再ACL连接成功后建立连接。基本链接流程如下: 一、创建连接 从用户点击扫描到的蓝牙设备开始&#xff0c;发起连接的设备会发送create_connection给controller。controller会进行page过程。 而后会底层链接完成&#xff0c;这个完成仅仅是确认…

CSS初级教程【第四天】

CSS初级教程【第四天】【1】CSS 外边距【2】CSS 外边距合并【3】所有 CSS 外边距属性【4】CSS 内边距【5】内边距和元素宽度【6】所有 CSS 内边距属性【7】CSS 高度和宽度【8】设置 CSS 尺寸属性CSS上回学习链接 CSS初级教程【第一天】 CSS初级教程【第二天】 CSS初级教程【第三…

windows本地安装openjdk环境

1讲一下为什么要选openjdk 先讲一下为什么小编在这里选择openjdk&#xff0c;而不是像绝大多数人一样选择jdk。那是因为&#xff0c;jdk在1.8的某个版本之后&#xff0c;就不是免费的版本了&#xff0c;不能免费用于商用。有过工作经验的就知道&#xff0c;公司一般对电脑上安装…

掌握这17张图,没人比你更懂RecyclerView的预加载

回顾上一篇文章&#xff0c;我们为了减少描述问题的维度&#xff0c;于演示之前附加了许多限制条件&#xff0c;比如禁用了RecyclerView的预拉取机制。 实际上&#xff0c;预拉取(prefetch)机制作为RecyclerView的重要特性之一&#xff0c;常常与缓存复用机制一起配合使用、共…

Go语言设计与实现 -- Mutex源码剖析

上图来自面向信仰编程 上图中&#xff0c;第一列为常见的同步原语&#xff0c;第二列为容器&#xff0c;第三列为互斥锁。 接下来我们来逐一介绍一下&#xff1a; Mutex 我们先来看一下sync.Mutex的结构体&#xff1a; type Mutex struct {// 当前互斥锁的状态state int32…

代码随想录算法训练营第一天 java : 704.二分查找法、27.移除算法

文章目录Leecode 704.二分查找题目连接&#xff1a;[Leecode 704.二分查找](https://leetcode.cn/problems/remove-element/)遇到的问题题目二分法的第一种写法 &#xff08;左闭右闭)第二种解法&#xff08;左闭右开 代码呈现&#xff09;Leecode 27.移除元素题目链接&#xf…

MyBatis【创建与使用】

MyBatis【创建与使用】&#x1f34e;一. MyBatis&#x1f352;1.1. MyBatis 是什么&#xff1f;&#x1f352;1.2 没有使用MyBatis时的操作流程&#x1f352;1.3 MyBatis的操作与数据库之间的流程&#x1f34e;二.创建MyBatis项目&#x1f352;2.1 idea创建&#x1f352;2.2 配…

【Git】一文带你入门Git分布式版本控制系统(撤销修改、删除文件)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

Debezium故障演练

1、搭建演练环境 postgresql及wal2json插件安装:https://blog.csdn.net/li281037846/article/details/128411222 kafka及kafka-connect安装&#xff0c;略 //添加debezium connector curl -i -X POST -H "Content-Type:application/json" -H "Accepted:applic…

Qt样式(qss)使用小结(软件换肤,比如暗黑模式)

1.背景&#xff1a; Qt style sheet&#xff08;qss&#xff09;跟前端技术一样&#xff0c;就是为了美化界面。关键是&#xff0c;太好用了。之前还为此写过一篇博客。 Qt样式&#xff08;qss&#xff09;手册小结_大橘的博客-CSDN博客 其中主要是记录如何获取手册细节。 …

6、GPIO输入按键检测(轮询检测)

目录 0x01、简介 0x02、硬件设计 0x03、编写函数 0x001、按键初始化 0x002、按键检测 0x003、按键led翻转 0x04、源程序下载地址 0x01、简介 本次实验主要实现按键控制LED灯。 由于机械按键在按下和抬起的时候会产生按键抖动&#xff0c;所以在设计的时候需要考虑如何消除抖…

Pytorch可视化特征图(代码 亲测可用)

2013年Zeiler和Fergus发表的《Visualizing and Understanding Convolutional Networks》 早期LeCun 1998年的文章《Gradient-Based Learning Applied to Document Recognition》中的一张图也非常精彩&#xff0c;个人觉得比Zeiler 2013年的文章更能给人以启发。从下图的F6特征&…

会议OA项目-首页

目录一、Flex布局简介什么是flex布局&#xff1f;flex属性学习地址&#xff1a;案例演示二、轮播图组件及mockjs三、会议OA小程序首页布局一、Flex布局简介 布局的传统解决方案&#xff0c;基于盒状模型&#xff0c;依赖 display属性 position属性 float属性 什么是flex布局…

简单有效的Mac内存清理方法,不用收藏也能记住

Mac电脑使用的时间越久&#xff0c;系统的运行就会变的越卡顿&#xff0c;这是Mac os会出现的正常现象&#xff0c;卡顿的原因主要是系统缓存文件占用了较多的磁盘空间&#xff0c;或者Mac的内存空间已满。如果你的Mac运行速度变慢&#xff0c;很有可能是因为磁盘内存被过度占用…