为什么非阻塞io性能更好_提高性能:流的非阻塞处理

为什么非阻塞io性能更好

1.简介

想象一下,我们有一个需要访问外部Web服务的应用程序,以便收集有关客户端的信息,然后对其进行处理。 更具体地说,我们无法在一次调用中获得所有这些信息。 如果我们要查找不同的客户端,则需要多次调用。

如下图所示,该示例应用程序将检索有关多个客户的信息,将它们分组在一个列表中,然后对其进行处理以计算其购买总额:

流

在这篇文章中,我们将看到收集信息的不同方法,并且从性能方面来看,哪一种是最好的。

这是与Java相关的文章。 但是,我们将使用Spring框架来调用RESTful Web服务。

栏目:

  1. 介绍
  2. 解释例子
  3. 首次尝试:顺序流
  4. 提高性能:并行流
  5. 具有CompletableFuture的非阻塞处理
  6. 结论

可以在Java 8 GitHub存储库中找到源代码。

此外,您可以访问此存储库中公开RESTful Web服务的Web应用程序的源代码。

2.解释示例

在我们的应用程序中,我们有20个ID的列表,这些ID表示我们要从Web服务检索的客户端。 检索完所有客户之后,我们将查看每个客户购买了什么,并对它们进行汇总以计算出所有客户花费的总金额。

但是,有一个问题,该Web服务每次调用仅允许检索一个客户端,因此我们将需要调用该服务20次。 此外,Web服务有点慢,至少需要两秒钟才能响应请求。

如果我们看一下实现Web服务的应用程序,我们可以看到调用是由ClientController类处理的:

@RestController
@RequestMapping(value="/clients")
public class ClientController {@Autowiredprivate ClientService service;@RequestMapping(value="/{clientId}", method = RequestMethod.GET)public @ResponseBody Client getClientWithDelay(@PathVariable String clientId) throws InterruptedException {Thread.sleep(2000);Client client = service.getClient(clientId);System.out.println("Returning client " + client.getId());return client;}
}

Thread.sleep用于模拟响应速度慢。

域类(客户)包含我们需要的信息; 客户花了多少钱:

public class Client implements Serializable {private static final long serialVersionUID = -6358742378177948329L;private String id;private double purchases;public Client() {}public Client(String id, double purchases) {this.id = id;this.purchases = purchases;}//Getters and setters
}

3.首次尝试:顺序流

在第一个示例中,我们将顺序调用服务以获取所有二十个客户端的信息:

public class SequentialStreamProcessing {private final ServiceInvoker serviceInvoker;public SequentialStreamProcessing() {this.serviceInvoker = new ServiceInvoker();}public static void main(String[] args) {new SequentialStreamProcessing().start();}private void start() {List<String> ids = Arrays.asList("C01", "C02", "C03", "C04", "C05", "C06", "C07", "C08", "C09", "C10", "C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20");long startTime = System.nanoTime();double totalPurchases = ids.stream().map(id -> serviceInvoker.invoke(id)).collect(summingDouble(Client::getPurchases));long endTime = (System.nanoTime() - startTime) / 1_000_000;System.out.println("Sequential | Total time: " + endTime + " ms");System.out.println("Total purchases: " + totalPurchases);}
}

输出:

Sequential | Total time: 42284 ms
Total purchases: 20.0

该程序的执行大约需要42秒。 这是太多时间。 让我们看看是否可以改善其性能。

4.提高性能:并行流

Java 8允许我们将流分成多个块,并在单独的线程中处理每个流。 我们需要做的就是简单地在上一个示例中将流创建为并行流。

您应考虑到每个块将在其线程中异步执行,因此处理这些块的顺序一定无关紧要。 在我们的案例中,我们正在汇总购买量,因此我们可以做到。

让我们尝试一下:

private void start() {List<String> ids = Arrays.asList("C01", "C02", "C03", "C04", "C05", "C06", "C07", "C08", "C09", "C10", "C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20");long startTime = System.nanoTime();double totalPurchases = ids.parallelStream().map(id -> serviceInvoker.invoke(id)).collect(summingDouble(Client::getPurchases));long endTime = (System.nanoTime() - startTime) / 1_000_000;System.out.println("Parallel | Total time: " + endTime + " ms");System.out.println("Total purchases: " + totalPurchases);
}

输出:

Parallel | Total time: 6336 ms
Total purchases: 20.0

哇,这是一个很大的进步! 但是这个数字是什么来的呢?

并行流在内部使用ForkJoinPool,它是Java 7中引入的ForkJoin框架所使用的池。默认情况下,该池使用与计算机处理器可以处理的线程数相同的线程。 我的笔记本电脑是可以处理8个线程的四核(您可以通过调用Runtime.getRuntime.availableProcessors进行检查),因此它可以并行地对Web服务进行8次调用。 由于我们需要20次调用,因此至少需要3次“回合”:

调用

好的,所以从40秒到6秒是一个不错的改进,但是,我们还能进一步改进吗? 答案是肯定的。

5.使用CompletableFuture进行非阻塞处理

让我们分析先前的解决方案。

我们发送8个线程来调用每个Web服务,但是当该服务正在处理请求时(整整两秒钟),我们的处理器除了等待外什么也不做(这是IO操作)。 在这些请求不回来之前,我们将无法发送更多请求。

问题是,如果我们可以异步发送所有20个请求,释放处理器并在可用时处理每个响应,该怎么办? 这是CompletableFuture抢救的地方:

public class AsyncStreamExecutorProcessing {private final ServiceInvoker serviceInvoker;private final ExecutorService executorService = Executors.newFixedThreadPool(100);public AsyncStreamExecutorProcessing() {this.serviceInvoker = new ServiceInvoker();}public static void main(String[] args) {new AsyncStreamExecutorProcessing().start();}private void start() {List<String> ids = Arrays.asList("C01", "C02", "C03", "C04", "C05", "C06", "C07", "C08", "C09", "C10", "C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20");long startTime = System.nanoTime();List<CompletableFuture<Client>> futureRequests = ids.stream().map(id -> CompletableFuture.supplyAsync(() -> serviceInvoker.invoke(id), executorService)).collect(toList());double totalPurchases = futureRequests.stream().map(CompletableFuture::join).collect(summingDouble(Client::getPurchases));long endTime = (System.nanoTime() - startTime) / 1_000_000;System.out.println("Async with executor | Total time: " + endTime + " ms");System.out.println("Total purchases: " + totalPurchases);executorService.shutdown();}
}

输出:

Async with executor | Total time: 2192 ms
Total purchases: 20.0

在上一个示例中花费了三分之一的时间。

我们同时发送了所有20个请求,因此在IO操作上花费的时间仅花费了一次。 收到回复后,我们会Swift对其进行处理。

使用执行程序服务很重要,它被设置为supplyAsync方法的可选第二个参数。 我们指定了一个包含一百个线程的池,因此我们可以同时发送100个请求。 如果我们不指定执行者,则默认情况下将使用ForkJoin池。

您可以尝试删除执行程序,您将看到与并行示例相同的性能。

六,结论

我们已经看到,当执行不涉及计算的操作(例如IO操作)时,我们可以使用CompletableFuture类来利用我们的处理器并提高应用程序的性能。

翻译自: https://www.javacodegeeks.com/2015/03/improving-performance-non-blocking-processing-of-streams.html

为什么非阻塞io性能更好

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

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

相关文章

linux history文件路径,Linux、Unix常用命令(文件和目录相关)

mkdir dirname 建立子目录. 注意:用户不能在一个不存在的目录中建立子目录。mkdir data 在当前目录下建立子目录 datamkdir /usr/data 在/usr/目录下建立子目录 data&#xff0c;此时/usr 目录必须已经存在。rmdirrmdir dirname 删除空目录&#xff0c;目录里面如有文件或目录则…

c语言的输入函数有哪些

c语言的输入函数有&#xff1a;1、scanf的返回值scanf()函数返回成功赋值的数据项数&#xff0c;读到文件末尾出错时则返回EOF。如&#xff1a;scanf("%d%d", &a, &b);如果a和b都被成功读入&#xff0c;那么scanf的返回值就是2如果只有a被成功读入&#xff0…

php cdi_CDI和EJB:在事务成功时发送异步邮件

php cdi再次问好&#xff01; :) 这次&#xff0c;我选择了一项常见任务&#xff0c;我认为大多数情况下都以错误的方式完成&#xff1a;发送电子邮件。 并非所有人都不知道电子邮件API的工作方式&#xff0c;例如JavaMail或Apache的commons-email 。 我通常看到的一个问题是&…

linux中多进程调试,linux下用gdb调试多进程

今天来学习一下linux下gdb如何调试多进程&#xff0c;在学习之前我我们能先看一张表&#xff1a;这张表是gdb调试的命令表&#xff0c;这对那些对gdb不熟的同学来说是非常有必要的。一、多进程调试的命令1、set follow-fork-mode parent|child因为gdb在一般情况下&#xff0c;只…

初学者宝典:C语言入门基础知识大全(下)

06类型的自动转换和强制转换当同一表达式中各数据的类型不同时&#xff0c;编译程序会自动把它们转变成同一类型后再进行计算。转换优先级为&#xff1a;char < int < float < double 即左边级别“低“的类型向右边转换。具体地说&#xff0c;若在表达式中优先级最高的…

linux接口 头文件,第一种:1、添加关键头文件:#include linux/of_gpio.h#include linux/gpio.h...

第一种&#xff1a;1、添加关键头文件&#xff1a;#include #include #include #include #include #include 2、在已经存在驱动文件中搜索"DEVICE_ATTR"关键字&#xff0c;如果存在&#xff0c;直接参考已经存在的方法添加一个即可&#xff0c;如下&#xff1a;unsig…

viewpager默认界面_使用默认方法的界面演变–第一部分:方法

viewpager默认界面几周前&#xff0c;我们详细研究了默认方法 -Java 8中引入的一项功能&#xff0c;该功能允许为接口方法提供实现&#xff0c;即方法主体&#xff0c;从而定义接口中的行为。 引入此功能是为了实现接口演进 。 在JDK的上下文中&#xff0c;这意味着在不破坏所…

C语言中scanf函数的3种常见问题与应对技巧

在写代码时难免对一些知识点不熟悉&#xff0c;导致犯错&#xff0c;今天分享几点小知识给大家。空白符问题#includeint main(void){int a;printf("input the data ");scanf("%d ",&a); //这里多了一个回车符printf("%d",a);return 0;}结果…

jpa和hibernate_JPA和Hibernate级联类型的初学者指南

jpa和hibernate介绍 JPA将实体状态转换转换为数据库DML语句。 由于对实体图进行操作很常见&#xff0c;因此JPA允许我们将实体状态更改从父级传播到子级 。 通过CascadeType映射配置此行为。 JPA与Hibernate级联类型 Hibernate支持所有JPA级联类型和一些其他旧式级联样式。 下…

linux find 权限不够,超级用户find: `/home/pipi/.gvfs': 权限不够

用sudo su命令切换成的根用户&#xff0c;在找某文件的时候报错&#xff1a;rootubuntu:/home/pipi# find / -perm -2000/sbin/unix_chkpwdfind: /home/pipi/.gvfs: 权限不够就是普通用户pipi的主目录下的一个叫 .gvfs 的目录&#xff0c;dr-x------ 2 pipi pipi 0 …

aws上部署hadoop_在AWS Elastic MapReduce上运行PageRank Hadoop作业

aws上部署hadoop在上一篇文章中&#xff0c;我描述了一个执行PageRank计算的示例&#xff0c;该示例是使用Apache Hadoop进行Mining Massive Dataset课程的一部分。 在那篇文章中&#xff0c;我接受了Java中现有的Hadoop作业&#xff0c;并做了一些修改&#xff08;添加了单元测…

linux编写一个简单的端口扫描程序,小弟我在linux下写了个简单的多线程端口扫描程序,运行时出现有关问题,请问一下(2)...

当前位置:我的异常网 Linux/Unix 小弟我在linux下写了个简单的多线程端口扫描程序&#xff0c;小弟我在linux下写了个简单的多线程端口扫描程序&#xff0c;运行时出现有关问题,请问一下(2)www.myexceptions.net 网友分享于&#xff1a;2013-02-26 浏览&#xff1a;23次usle…

在嵌套使用if语句时,C语言规定else总是什么?

C语言的语法规定&#xff1a;else子句总是与前面最近的不带else的if相结合&#xff0c;与书写格式无关。在C语言中&#xff0c;使用if和else关键字对条件进行判断。请先看下面的代码&#xff1a;#include int main(){ int age; printf("请输入你的年龄&#xff1a;&…

optional空值判断_Java 8 Optional不仅用于替换空值

optional空值判断总览 在Java 8中&#xff0c;您可以返回Optional而不是返回null。 就像您在Java 7中所做的那样。这可能会有所不同&#xff0c;这取决于您是否倾向于忘记检查null还是使用静态代码分析来检查nullalbe引用。 但是&#xff0c;还有一种更引人注目的情况是将Opti…

continue语句的作用是结束整个循环的执行吗?

continue 语句的作用是结束本次循环&#xff0c;跳过循环体中剩余的语句而强制进入下一次循环&#xff08;回到循环体的开头准备再次执行循环体&#xff09;。continue语句只用在 while、for 循环中&#xff0c;常与 if 条件语句一起使用&#xff0c;判断条件是否成立。使用方式…

linux 远程权限不够,Eclipse连接远程Hadoop集群开发时权限不足问题解决方案

eclipse连接远程Hadoop集群开发时报错Exception in thread "main" org.apache.hadoop.security.AccessControlException: Permission denied: userd, accessWRITE, inode"data":zxg:supergroup:rwxr-xr-xat org.apache.hadoop.hdfs.server.namenode.FSPerm…

jsp导入jstl标签库_EE JSP:使用JSTL标记库生成动态内容

jsp导入jstl标签库除了在JSP中编写自己的定制标记之外&#xff0c;您还将发现Java EE实际上提供了一组Java标准标记库&#xff08;JSTL&#xff09;供您使用。 这些内置标签包括重复&#xff08;for-loop&#xff09;标签&#xff0c;条件标签&#xff0c;变量声明和输出标签等…

一文掌握 C 智能指针的使用

RAII 与引用计数了解 objective-C/Swift 的程序员应该知道引用计数的概念。引用计数这种计数是为了防止内存泄露而产生的。基本想法是对于动态分配的对象&#xff0c;进行引用计数&#xff0c;每当增加一次对同一个对象的引用&#xff0c;那么引用对象的引用计数就会增加一次&a…

linux里面启用无线网卡,linux启用无线网卡上网

1、使用cat /proc/version查看linux内核版本号&#xff0c;我的系统是Linux version 2.6.32-220.el6.i6862、使用cat /etc/issue查看linux发行版本号&#xff0c;我的系统是Red Hat Enterprise Linux Server release 6.2 (Santiago)现在 进入正题&#xff0c;如何在redhat linu…

fwrite函数的一般调用形式是什么?

fwrite() 是C 语言标准库中的一个文件处理函数&#xff0c;功能是向指定的文件中写入若干数据块&#xff0c;如成功执行则返回实际写入的数据块数目。该函数以二进制形式对文件进行操作&#xff0c;不局限于文本文件。语法&#xff1a;fwrite(buffer,size,count,fp)参数&#x…