面试官: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 Apache 开发者
解决java.lang.IllegalArgumentException: Invalid uri由无效查询引起的问题
最后,当你修改代码以避免这个异常时,保持代码的整洁和可读性同样重要。注释你的代码,用意图清晰的方法名,并确保逻辑简单明了,这样在未来你或其他开发者需要时可以轻松地维护它。
290 20
|
4月前
|
SQL Java 数据库
解决Java Spring Boot应用中MyBatis-Plus查询问题的策略。
保持技能更新是侦探的重要素质。定期回顾最佳实践和新技术。比如,定期查看MyBatis-Plus的更新和社区的最佳做法,这样才能不断提升查询效率和性能。
167 1
|
9月前
|
SQL NoSQL Java
Java使用sql查询mongodb
通过MongoDB Atlas Data Lake或Apache Drill,可以在Java中使用SQL语法查询MongoDB数据。这两种方法都需要适当的配置和依赖库的支持。希望本文提供的示例和说明能够帮助开发者实现这一目标。
299 17
|
9月前
|
SQL Java 数据库连接
【潜意识Java】MyBatis中的动态SQL灵活、高效的数据库查询以及深度总结
本文详细介绍了MyBatis中的动态SQL功能,涵盖其背景、应用场景及实现方式。
863 6
|
10月前
|
SQL NoSQL Java
Java使用sql查询mongodb
通过使用 MongoDB Connector for BI 和 JDBC,开发者可以在 Java 中使用 SQL 语法查询 MongoDB 数据库。这种方法对于熟悉 SQL 的团队非常有帮助,能够快速实现对 MongoDB 数据的操作。同时,也需要注意到这种方法的性能和功能限制,根据具体应用场景进行选择和优化。
371 9
|
11月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
11月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
11月前
|
SQL Java
使用java在未知表字段情况下通过sql查询信息
使用java在未知表字段情况下通过sql查询信息
123 8
|
11月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
260 4
|
12月前
|
SQL Java 关系型数据库
java连接mysql查询数据(基础版,无框架)
【10月更文挑战第12天】该示例展示了如何使用Java通过JDBC连接MySQL数据库并查询数据。首先在项目中引入`mysql-connector-java`依赖,然后通过`JdbcUtil`类中的`main`方法实现数据库连接、执行SQL查询及结果处理,最后关闭相关资源。
710 6