Java8实战-总结44

Java8实战-总结44

  • CompletableFuture:组合式异步编程
    • Future 接口
      • Future 接口的局限性
      • 使用 CompletableFuture 构建异步应用

CompletableFuture:组合式异步编程

最近这些年,两种趋势不断地推动我们反思我们设计软件的方式。第一种趋势和应用运行的硬件平台相关,第二种趋势与应用程序的架构相关,尤其是它们之间如何交互。随着多核处理器的出现,提升应用程序处理速度最有效的方式是编写能充分发挥多核能力的软件。通过切分大型的任务,让每个子任务并行运行,这一目标是能够实现的;相对直接使用线程的方式,使用分支/合并框架(在Java 7中引入)和并行流(在Java 8中新引入)能以更简单、更有效的方式实现这一目标。

第二种趋势反映在公共API日益增长的互联网服务应用。著名的互联网大鳄们纷纷提供了自己的公共API服务,比如谷歌提供了地理信息服务,Facebook提供了社交信息服务,Twitter提供了新闻服务。现在,很少有网站或者网络应用会以完全隔离的方式工作。更多的时候,下一代网络应用都采用“混聚”(mash-up)的方式:它会使用来自多个来源的内容,将这些内容聚合在一起,方便用户的生活。

比如,你可能希望为你的法国客户提供指定主题的热点报道。为实现这一功能,你需要向谷歌或者TwitterAPI请求所有语言中针对该主题最热门的评论,可能还需要依据你的内部算法对它们的相关性进行排序。之后,可能还需要使用谷歌的翻译服务把它们翻译成法语,甚至利用谷歌地图服务定位出评论作者的位置信息,最终将所有这些信息聚集起来,呈现在你的网站上。

当然,如果某些外部网络服务发生响应慢的情况,你希望依旧能为用户提供部分信息,比如提供带问号标记的通用地图,以文本的方式显示信息,而不是呆呆地显示一片空白屏幕,直到地图服务器返回结果或者超时退出。下图解释了这种典型的“混聚”应用如何与所需的远程服务交互。
在这里插入图片描述
要实现类似的服务,你需要与互联网上的多个Web服务通信。可是,你并不希望因为等待某些服务的响应,阻塞应用程序的运行,浪费数十亿宝贵的CPU时钟周期。比如,不要因为等待Facebook的数据,暂停对来自Twitter的数据处理。

这些场景体现了多任务程序设计的另一面。前面介绍的分支/合并框架以及并行流是实现并行处理的宝贵工具;它们将一个操作切分为多个子操作,在多个不同的核、CPU甚至是机器上并行地执行这些子操作。

与此相反,如果你的意图是实现并发,而非并行,或者你的主要目标是在同一个CPU上执行几个松耦合的任务,充分利用CPU的核,让其足够忙碌,从而最大化程序的吞吐量,那么你其实真正想做的是避免因为等待远程服务的返回,或者对数据库的查询,而阻塞线程的执行,浪费宝贵的计算资源,因为这种等待的时间很可能相当长。Future接口,尤其是它的新版实现CompletableFuture,是处理这种情况的利器。下图说明了并行和并发的区别。
在这里插入图片描述

Future 接口

Future接口在Java 5中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在Future中触发那些潜在耗时的操作把调用线程解放出来,让它能继续执行其他有价值的工作,不再需要呆呆等待耗时的操作完成。打个比方,你可以把它想象成这样的场景:你拿了一袋子衣服到你中意的干洗店去洗。干洗店的员工会给你张发票,告诉你什么时候你的衣服会洗好(这就是一个Future事件)。衣服干洗的同时,你可以去做其他的事情。Future的另一个优点是它比更底层的Thread更易用。要使用Future,通常你只需要将耗时的操作封装在一个Callable对象中,再将它提交给ExecutorService,就万事大吉了。下面这段代码展示了Java 8之前使用Future的一个例子。

ExecutorService executor = Executors.newCachedThreadPool(); 
Future<Double> future = executor.submit(new Callable<Double>() { public Double call() {return doSomeLongComputation(); }}); 
doSomethingElse(); try { Double result = future.get(1, TimeUnit.SECONDS); //获取异步操作的结果,如果最终被阻塞,无法得到结果,那么在最多等待1秒钟之后退出
} catch (ExecutionException ee) {// 计算抛出一个异常
} catch (InterruptedException ie) { // 当前线程在等待过程中被中断
} catch (TimeoutException te) { // 在Future对象完成之前超过已过期
}

正像上图介绍的那样,这种编程方式让你的线程可以在ExecutorService以并发方式调用另一个线程执行耗时操作的同时,去执行一些其他的任务。接着,如果你已经运行到没有异步操作的结果就无法继续任何有意义的工作时,可以调用它的get方法去获取操作的结果。如果操作已经完成,该方法会立刻返回操作的结果,否则它会阻塞你的线程,直到操作完成,返回相应的结果。

这种场景存在怎样的问题?如果该长时间运行的操作永远不返回了会怎样?为了处理这种可能性,虽然Future提供了一个无需任何参数的get方法,还是推荐大家使用重载版本的get方法,它接受一个超时的参数,通过它,可以定义线程等待Future结果的最长时间,而不是像上述代码中那样永无止境地等待下去。
在这里插入图片描述

Future 接口的局限性

通过第一个例子,我们知道Future接口提供了方法来检测异步计算是否已经结束(使用isDone方法),等待异步操作结束,以及获取计算的结果。但是这些特性还不足以让你编写简洁的并发代码。比如,很难表述Future结果之间的依赖性;从文字描述上这很简单,“当长时间计算任务完成时,请将该计算的结果通知到另一个长时间运行的计算任务,这两个计算任务都完成后,将计算的结果与另一个查询操作结果合并”。但是,使用Future中提供的方法完成这样的操作又是另外一回事。这也是需要更具描述能力的特性的原因,比如下面这些:

  • 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。
  • 等待Future集合中的所有任务都完成。
  • 仅等待Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。
  • 通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
  • 应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)。

使用 CompletableFuture 构建异步应用

为了展示CompletableFuture的强大特性,创建一个名为“最佳价格查询器”(best-price-finder)的应用,它会查询多个在线商店,依据给定的产品或服务找出最低的价格。这个过程中,你会学到几个重要的技能:

  • 首先,你会学到如何为你的客户提供异步API(如果你拥有一间在线商店的话,这是非常有帮助的)。

  • 其次,你会掌握如何让你使用了同步API的代码变为非阻塞代码。你会了解如何使用流水线将两个接续的异步操作合并为一个异步计算操作。这种情况肯定会出现,比如,在线商店返回了你想要购买商品的原始价格,并附带着一个折扣代码——最终,要计算出该商品的实际价格,你不得不访问第二个远程折扣服务,查询该折扣代码对应的折扣比率。

  • 你还会学到如何以响应式的方式处理异步操作的完成事件,以及随着各个商店返回它的商品价格,最佳价格查询器如何持续地更新每种商品的最佳推荐,而不是等待所有的商店都返回他们各自的价格(这种方式存在着一定的风险,一旦某家商店的服务中断,用户可能遭遇白屏)。

     同步API与异步API 同步API其实只是对传统方法调用的另一种称呼:你调用了某个方法,调用方在被调用方运行的过程中会等待,被调用方运行结束返回,调用方取得被调用方的返回值并继续运行。即使调用方和被调用方在不同的线程中运行,调用方还是需要等待被调用方结束运行,这就是阻塞式调用这个名词的由来。与此相反,异步API会直接返回,或者至少在被调用方计算完成之前,将它剩余的计算任务交给另一个线程去做,该线程和调用方是异步的——这就是非阻塞式调用的由来。执行剩余计算任务的线程会将它的计算结果返回给调用方。返回的方式要么是通过回调函数,要么是由调用方再次执行一个“等待,直到计算完成”的方法调用。这种方式的计算在I/O系统程序设计中非常常见:你发起了一次磁盘访问,这次访问和你的其他计算操作是异步的,你完成其他的任务时,磁盘块的数据可能还没载入到内存,你只需要等待数据的载入完成。
    

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

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

相关文章

js遍历数组和对象的常用方法

遍历数组的常用方法 for循环遍历&#xff1a;使用最广泛的方法&#xff0c;通过下标访问每一个数组元素。 const arr [1, 2, 3, 4, 5]; for(let i0; i<arr.length; i) {console.log(arr[i]); }forEach遍历&#xff1a;数组的forEach()方法可以遍历每个元素&#xff0c;对每…

微信小程序设计之主体文件app-wxss/less

一、新建一个项目 首先&#xff0c;下载微信小程序开发工具&#xff0c;具体下载方式可以参考文章《微信小程序开发者工具下载》。 然后&#xff0c;注册小程序账号&#xff0c;具体注册方法&#xff0c;可以参考文章《微信小程序个人账号申请和配置详细教程》。 在得到了测…

注解配置SpringMVC

文章目录 目的1、创建初始化类&#xff0c;代替web.xml2、创建SpringConfig配置类&#xff0c;代替spring的配置文件3、创建WebConfig配置类&#xff0c;代替SpringMVC的配置文件4、测试功能 目的 使用配置类和注解代替web.xml和SpringMVC配置文件的功能 1、创建初始化类&…

STM32F4X之中断二

一、外部中断 外部中断&#xff1a;外部中断的中断是相对于外部中断控制器&#xff08;EXTI&#xff09;来说&#xff0c;如下图所示&#xff1a; EXTI掌管着23根中断线&#xff0c;具体分布图下&#xff1a; 16根连接GPIO口&#xff0c;如下图&#xff1a; 所有的0口连接到中…

ORACLE表空间说明及操作

ORACLE 表空间作用 数据存储&#xff1a;表空间是数据库中存储数据的逻辑结构。它提供了用于存储表、索引、视图、存储过程等数据库对象的空间。通过划分数据和索引等对象的存储&#xff0c;可以更好地管理和组织数据库的物理存储结构。性能管理和优化&#xff1a;通过将不同类…

餐饮外卖小程序商城的作用是什么

随着互联网及线上餐饮的发展趋势&#xff0c;行业洗牌正在加速&#xff0c;并且对餐饮连锁门店提出更高要求&#xff0c;餐饮数字化转型加快&#xff0c;积极发展线上经营是不少餐饮商家的首选。这其中&#xff0c;餐饮外卖商城成为很多餐饮品牌的线上经营品牌&#xff0c;也是…

内核进程初始化和创建

task_struct 1.进程的状态 分时技术进行多进程调度 重点&#xff1a;进程的创建是如何的&#xff1f; linux在初始化的过程中那个会进行0号进程的创建&#xff0c;fork sched_init(); 做了什么 内核态 不可抢占 用户态 可以抢占 move_to_user_mode(); 把内核状态切换到…

JackSon工具类

JackSon工具类 一、简介 Jackson 是当前用的比较广泛的&#xff0c;用来序列化和反序列化 json 的 Java 的开源框架。 Jackson 的核心模块由三部分组成&#xff1a; jackson-core&#xff0c;核心包&#xff0c;提供基于"流模式"解析的相关 API&#xff0c;它包括…

VR全景应用广泛体现在哪里?有何优势?

VR全景作为一种新型营销方式&#xff0c;正在逐渐走进人们的视线&#xff0c;它区别于以往单一角度的照片和视频&#xff0c;VR全景制作显得更加直观、更加真实、更加生动。VR全景通过VR技术将所拍摄的图片变成720度可观看的场景模式&#xff0c;把产品的特色以及魅力整体呈现展…

封装axios的两种方式

作为前端工程师&#xff0c;经常需要对axios进行封装以满足复用的目的。在不同的前端项目中使用相同的axios封装有利于保持一致性&#xff0c;有利于数据之间的传递和处理。本文提供两种对axios进行封装的思路。 1. 将请求方式作为调用参数传递进来 首先导入了axios, AxiosIn…

对长度为n的顺序表L,编写一个时间复杂度为O(n),空间复杂度为O(1)的算法,该算法删除线性表中的所有值为x的数据元素

对长度为n的顺序表L&#xff0c;编写一个时间复杂度为O(n)&#xff0c;空间复杂度为O(1)的算法&#xff0c;该算法删除线性表中的所有值为x的数据元素 算法思路&#xff1a; 用count标记遇到x的次数&#xff0c;每次遇到x&#xff0c;count 遇到非x的元素&#xff0c;把它前移…

vue3后台管理系统之顶部tabbar组件搭建

1.1静态页面搭建 <template><div class"tabbar"><div class"tabbar_left"><!-- 面包屑 --><Breadcrumb /></div><div class"tabbar_right"><!-- 设置 --><Setting /></div></di…

自动驾驶的未来展望和挑战

自动驾驶技术是一项引人瞩目的创新&#xff0c;将在未来交通领域产生深远影响。然而&#xff0c;随着技术的不断演进&#xff0c;自动驾驶也面临着一系列挑战和障碍。本文将探讨自动驾驶的未来发展方向、技术面临的挑战&#xff0c;以及自动驾驶对社会和环境的潜在影响。 自动驾…

封装一个vue3 Toast组件,支持组件和api调用

先来看一段代码 components/toast/index.vue <template><div v-if"isShow" class"toast">{{msg}}</div> </template><script setup> import { ref, watch } from vue const props defineProps({show: {type: Boolean,def…

docker搭建个人镜像仓库

docker搭建个人镜像仓库 安装registry mkdir docker-registry cd docker-registry mkdir registry mkdr auth vim docker-compose.ymldocker-compose.yml的内容如下&#xff1a; version: 3 services:registry:image: registrycontainer_name: registryvolumes:- ./registry…

GPT带我学-设计模式-10观察者模式

1 请你介绍一下观察者模式 观察者模式&#xff08;Observer Pattern&#xff09;是一种设计模式&#xff0c;它定义了对象之间的一对多依赖关系&#xff0c;当一个对象&#xff08;被观察者&#xff09;的状态发生改变时&#xff0c;所有依赖于它的对象&#xff08;观察者&…

Canvas的缓冲区

Canvas的缓冲区 canvas的绘制缓冲区和显示缓冲区理解为两个容器&#xff0c;它们用于存储绘制结果。绘制缓冲区是用于存储渲染管线生成的像素数据&#xff0c;而显示缓冲区是绘制缓冲区最终的存储位置。 绘制缓冲区&#xff08;Drawing Buffer&#xff09;: 它是在 canvas 元…

JVM 类的加载子系统

文章目录 类的加载过程加载阶段链接阶段初始化 类的加载器测试代码中获取对应的加载器获取加载器加载的路径不同类对应的加载器自定义加载器自定义加载器的方式 获取类的加载器的方式双亲委派机制双亲委派机制的好处 Java 的 SPI 机制1. 接口定义2. 具体实现3. 配置 META-INF/s…

Redis中设置Hash数据类型的过期时间

1 方案 可以先对key进行赋值&#xff0c;然后对key设置一个过期时间。 &#xff08;1&#xff09;依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.7.3</version></dependency>…

Kubernetes (K8S)概述

目录 1、K8S 是什么&#xff1f; 1.1 作用 1.2 由来 1.3 含义 1.4 官网 2、为什么要用 K8S? 2.1 K8S 解决了裸跑Docker 的若干痛点 2.2 K8S主要功能如下 2.3 K8S 的特性 3、Kubernetes 集群架构与组件 3.1 核心组件 3.1.1 Master 组件 3.1.2 控制器主要包括 3.1…