谈谈JAVA中的同步和异步编码模型

#头条创作挑战赛#

前言

在计算机的早期阶段,单处理器系统占据主导地位,许多早期的编程语言都采用了同步编程模型。但是随着硬件的进步,出现了多核的处理器,这就需要开发更好的编程模型。一个能够充分利用所有资源的强大模型将是最合适的模型,这也催生了异步编程模型。

对于初学者来说,很难理解这两种编程模型之间的区别。在这篇文章中,我将尝试用通俗易懂的术语来简化和解释差异。我们还将看一下 Java 中的一个简单示例,了解 Java 中为异步编程提供的 API。

真实世界类比

在深入研究编程模型之前,我们先通过现实中的例子了解下同步和异步系统。

我们都去过超市商店购买我们的日常用品,每个人都用手推车收集物品,然后去柜台,柜台人员会扫描所有物品,准备账单,接受该人的付款,最后给出收据。对于每个客户来说,这部分都是一个耗时的过程。此外,所花费的时间与商品数量、任何技术故障和找零钱(现金支付)的时间成正比。

想象一下,如果是节假日,超市打折会怎么样? 客户将不得不排队,花费大量时间,就像下面的图片一样:

在同步系统中,需要等待前一个任务完成,才能处理下一个任务,这会导致系统的瓶颈并降低系统的吞吐量。

麦当劳设有自助服务亭终端,客户可以下订单并完成付款。付款完成后,将随订单号一起分发收据。订单准备就绪后,屏幕上会显示订单号。

在准备食物的同时,顾客可以同时做多件事。他们不会被准备订单的人挡住,因此商店可以避免拥挤。客户不会按顺序收到他们的订单。例如: 一个人可能会在另一个人之前收到他的食物,即使他会在他之后下订单。这是异步系统的一个例子。

异步系统不会等待前一个任务完成,当给定任务正在进行时,它会继续执行另一项任务。

同步编程模型

在同步编程中,每一行代码都是按顺序执行的。在网络或文件 IO 调用的情况下,执行线程会阻塞。让我们看一下同步代码执行的示例。

我们将以电子商务系统为例。假设我们有一个函数,可以根据订单获取所有商品。然后它会为这些项目创建一个 CSV 文件并存储它。此外,它通过电子邮件将订单状态信息发送给客户。在上述情况下,我们的代码将按顺序执行,即第 10、11 和 12 行。

这些fetchItems函数执行数据库调用。数据库调用代价很大,因为它是 IO 调用。一旦我们调用该fetchItems方法,主线程就会阻塞。直到从数据库成功返回所有项目,它才恢复。

该createCsvFileForItems函数处理项目并生成 CSV 文件。该函数再次将主线程置于等待状态。一旦文件创建成功,该函数将返回。此外,它将调用该sendOrderStatus方法。

该方法sendOrderStatus主要是使用电子邮件地址并将订单状态发送给客户。

假设每个函数所花费的时间如下:

程序完成所需的总时间为 23 毫秒,sendOrderStatus函数将在 15 毫秒后调用。实际上sendOrderStatus功能是比较独立。但是,它仍然需要等待前面两个功能完成。假如我们先调用sendOrderStatus,在这种情况下,fetchItems函数必须等待 8 毫秒。

每个功能花费的时间

此外,当函数被调用时,主线程会阻塞。当线程进入等待状态时,它无法做任何有用的工作。如果应用程序在多核 CPU 上运行,则只会使用一个 CPU。这会导致其他 CPU 闲置。

通过在不同线程中执行独立任务,我们可以充分利用所有 CPU 内核。在上面的示例中,这可以通过并行运行fetchItems和sendOrderStatus来完成。完成后,我们可以使用结果然后调用createCsvFileForItems。

异步编程模型

在异步编程中,独立的任务在不同的线程上并行执行。一旦任务完成并返回结果,相关任务就会作为回调被调用。独立任务不会阻塞主线程,我们可以利用 CPU 的所有核心。

在上面的代码中,我们有每个方法的异步版本。该代码不会依次执行第 32、33 和 34 行。它将调用fetchItemsAsync方法新起一个线程从数据库中获取数据。随后,它将调用该sendOrderStatusAsync方法而无需等待第一个函数完成,此方法将在不同 CPU 上的单独线程上运行。在第 33 行,该fetchItemsAsync方法返回一个Future。主线程将在第 35 行等待这个Future并获取它的值。

该功能的执行将如下图所示:

异步函数执行

Java 中的异步编程

Java 提供了实现异步编程的接口和类,如CompletionStage、CompletableFuture ,表示返回一个异步执行未来的结果。

在上面的示例中,该方法fetchItemsAsync返回了Future>,简单理解未来会返回的数据。此方法定义并初始化一个 CompletableFuture。在 Executor API 的帮助下,它将执行启动一个新线程运行。计算完成后,它会完成 future(第 48 行),然后返回它。

由于fetchItemsAsync是异步的,主线程可以同时调用sendOrderStatus 方法。因此,它不需要等待前一种方法完成。然后它会在以后使用get方法得到最终的结果。主线程将阻塞调用 get 方法,并在结果可用后恢复。

我们可以避免上述样板代码并使用 CompletableFuture 的 API。CompletableFuture 提供方法supplyAsync和runAsync。Supplier 接口是一个不接受任何参数的功能接口。它返回参数化类型的值。

使用可完成的期货

supplyAsync我们在上面的代码片段中使用函数。在内部,此函数将在一个线程中从 java 中的 ForkJoinPool 执行代码,该thenApply构造处理异步计算阶段的结果。

如上所示,fetchItems调用异步返回项目列表。然后将结果提供给createCsvFileForItems函数。此函数充当回调函数,并在fetchItems函数完成后被调用。程序开始执行函数sendOrderStatus,无需等待第一阶段的计算完成。因此,我们使用异步编程解耦了两个独立的任务。

总结

我们通过上面简单讲解了同步和异步编程模型,我们来总结下各自的优缺点。

同步编程模型优点

同步编程模型缺点

异步编程模型优点

异步编程模型缺点

展开阅读全文

页面更新:2024-04-26

标签:多核   模型   线程   主线   函数   订单   独立   代码   方法   系统

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top