面试官:java8中parallelStream提升数倍查询效率是怎样实现的

简介: 业务场景在很多项目中,都有类似数据汇总的业务场景,查询今日注册会员数,在线会员数,订单总金额,支出总金额等。。。这些业务通常都不是存在同一张表中,我们需要依次查询出来然后封装成所需要的对象返回给前端。那么在此过程中,就可以把这个接口中“大任务”拆分成N个小任务,异步执行这些小任务,等到最后一个小任务执行完,把所有任务的执行结果封装到返回结果中,统一返回到前端展示。

业务场景


在很多项目中,都有类似数据汇总的业务场景,查询今日注册会员数,在线会员数,订单总金额,支出总金额等。。。这些业务通常都不是存在同一张表中,我们需要依次查询出来然后封装成所需要的对象返回给前端。那么在此过程中,就可以把这个接口中“大任务”拆分成N个小任务,异步执行这些小任务,等到最后一个小任务执行完,把所有任务的执行结果封装到返回结果中,统一返回到前端展示。

image.png


同步执行


首先看看同步执行的代码

publicclassTest{
@Data    @NoArgsConstructor    @AllArgsConstructor    @ToStringclassResult{
/**
        * 在线人数
        */
Integer onlineUser;/**
        * 注册人数
        */
Integer registered;/**
        * 订单总额
        */
BigDecimal orderAmount;/**
        * 支出总额
        */
BigDecimal outlayAmount;    }    @org.junit.Test
publicvoidcollect(){
System.out.println("数据汇总开始");
longstartTime = System.currentTimeMillis();
Integer onlineUser = queryOnlineUser();        Integer registered = queryRegistered();        BigDecimal orderAmount = queryOrderAmount();        BigDecimal outlayAmount = queryOutlayAmount();        Result result =newResult(onlineUser, registered, orderAmount, outlayAmount);
longendTime = System.currentTimeMillis();
System.out.println("获取汇总数据结束,result = "+ result);
System.out.println("总耗时 = "+ (endTime - startTime) +"毫秒");
}publicIntegerqueryOnlineUser(){
try{
Thread.sleep(2000);
}catch(InterruptedException e) {
e.printStackTrace();        }        System.out.println("查询在线人数 耗时2秒");
return10;
}publicIntegerqueryRegistered(){
try{
Thread.sleep(2000);
}catch(InterruptedException e) {
e.printStackTrace();        }        System.out.println("查询注册人数 耗时2秒");
return10086;
}publicBigDecimalqueryOrderAmount(){
try{
Thread.sleep(3000);
}catch(InterruptedException e) {
e.printStackTrace();        }        System.out.println("查询订单总额 耗时3秒");
returnBigDecimal.valueOf(2000);
}publicBigDecimalqueryOutlayAmount(){
try{
Thread.sleep(3000);
}catch(InterruptedException e) {
e.printStackTrace();        }        System.out.println("查询支出总额 耗时3秒");
returnBigDecimal.valueOf(1000);
}}  

执行时长想必大家都能够想得到,理所应当是10秒以上

数据汇总开始查询在线人数 耗时2秒

查询注册人数 耗时2秒

查询订单总额 耗时3秒

查询支出总额 耗时3秒

获取汇总数据结束,result = Test.Result(onlineUser=10, registered=10086, orderAmount=2000, outlayAmount=1000)

总耗时 =10008毫秒

image.png


异步执行


下面换成异步执行,用java8的parallelStream(并行流),这里为什么不用Thread呢,这里有一个注意点,我们需要获取所有所有子任务执行完的时间点,在这个时间点之后才能将结果封装返回,Thread没有办法满足,这里parallelStream和函数式接口就登场了。

java8的特性之一 —— lambda表达式,就是配合函数式接口使用的。

java8内置了四大核心函数式接口:

1、Consumer : 消费型接口 void accept(T t);

2、Supplier : 供给型接口 T get();

3、Function<T,R> : 函数型接口 R apply(T t);

4、Predicate : 断言型接口 boolean test(T t);

这四大核心函数式接口其下还有很多子接口,基本上能满足日常项目所用,这里扯远了。。 直接上代码。

这里我们需要使用的是Runable接口,是无参无返回值的一个接口。在实际场景中,可能有时间范围之类的查询参数的,则可以根据不同业务使用不同的接口。这种方式也可以用Future接口去实现,有兴趣的可以试一试,这里就不多做叙述了。

@org.junit.Test
public void collect() {    System.out.println("数据汇总开始");
long startTime = System.currentTimeMillis();    Result result =newResult();
List taskList =newArrayList() {
{            add(()->result.setOnlineUser(queryOnlineUser()));
add(()->result.setRegistered(queryRegistered()));
add(()->result.setOrderAmount(queryOrderAmount()));
add(()->result.setOutlayAmount(queryOutlayAmount()));
}    };    taskList.parallelStream().forEach(v -> v.run());    long endTime = System.currentTimeMillis();    System.out.println("获取汇总数据结束,result = "+ result);
System.out.println("总耗时 = "+ (endTime - startTime) +"毫秒");
}

执行结果,由于四个子任务都是并行的,效率直接提升了三倍,如果子任务越多的话提升效果越明显。

数据汇总开始
查询在线人数 耗时2秒
查询注册人数 耗时2秒
查询订单总额 耗时3秒
查询支出总额 耗时3秒
获取汇总数据结束,result = Test.Result(onlineUser=10, registered=10086, orderAmount=2000, outlayAmount=1000)
总耗时 =3079毫秒


总结


1.parallelStream是异步编程的好帮手,在使用过程中一定要注意线程安全的问题。

2.以上这种方式只能用在没有事务的业务中,因为在多线程中,事务是不共享的。

image.png

相关文章
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
11天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
12天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
38 4
|
24天前
|
SQL Java 关系型数据库
java连接mysql查询数据(基础版,无框架)
【10月更文挑战第12天】该示例展示了如何使用Java通过JDBC连接MySQL数据库并查询数据。首先在项目中引入`mysql-connector-java`依赖,然后通过`JdbcUtil`类中的`main`方法实现数据库连接、执行SQL查询及结果处理,最后关闭相关资源。
|
1月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
69 2
|
29天前
|
缓存 Java 数据处理
java查询大量数据优化
通过结合的高性能云服务,如其提供的弹性计算资源与全球加速网络,可以进一步增强这些优化策略的效果,确保数据处理环节更加迅速、可靠。蓝易云不仅提供稳定的基础架构,还拥有强大的安全防护和灵活的服务选项,是优化大型数据处理项目不可或缺的合作伙伴。
27 0
|
2月前
|
SQL Java
使用java在未知表字段情况下通过sql查询信息
使用java在未知表字段情况下通过sql查询信息
33 1
|
1月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
28 0
|
3月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
3月前
|
Java
【Java基础面试三十七】、说一说Java的异常机制
这篇文章介绍了Java异常机制的三个主要方面:异常处理(使用try、catch、finally语句)、抛出异常(使用throw和throws关键字)、以及异常跟踪栈(异常传播和程序终止时的栈信息输出)。