zoukankan      html  css  js  c++  java
  • java8的parallelStream提升数倍查询效率

    业务场景

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

    同步执行

    首先看看同步执行的代码

    public class Test {
    
    
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        @ToString
        class Result {
            /**
             * 在线人数
             */
            Integer onlineUser;
    
            /**
             * 注册人数
             */
            Integer registered;
    
            /**
             * 订单总额
             */
            BigDecimal orderAmount;
    
            /**
             * 支出总额
             */
            BigDecimal outlayAmount;
        }
    
        @org.junit.Test
        public void collect() {
            System.out.println("数据汇总开始");
            long startTime = System.currentTimeMillis();
            Integer onlineUser = queryOnlineUser();
            Integer registered = queryRegistered();
            BigDecimal orderAmount = queryOrderAmount();
            BigDecimal outlayAmount = queryOutlayAmount();
            Result result = new Result(onlineUser, registered, orderAmount, outlayAmount);
            long endTime = System.currentTimeMillis();
            System.out.println("获取汇总数据结束,result = " + result);
            System.out.println("总耗时 = " + (endTime - startTime) + "毫秒");
        }
    
        public Integer queryOnlineUser() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("查询在线人数 耗时2秒");
            return 10;
        }
    
        public Integer queryRegistered() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("查询注册人数 耗时2秒");
            return 10086;
        }
    
        public BigDecimal queryOrderAmount() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("查询订单总额 耗时3秒");
            return BigDecimal.valueOf(2000);
        }
    
        public BigDecimal queryOutlayAmount() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("查询支出总额 耗时3秒");
            return BigDecimal.valueOf(1000);
        }
    
    }

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

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

    异步执行

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

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

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

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

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

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

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

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

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

    @org.junit.Test
    public void collect() {
        System.out.println("数据汇总开始");
        long startTime = System.currentTimeMillis();
        Result result = new Result();
        List<Runnable> taskList = new ArrayList<Runnable>() {
            {
                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.以上这种方式只能用在没有事务的业务中,因为在多线程中,事务是不共享的。

  • 相关阅读:
    优化SQL查询:如何写出高性能SQL语句
    提高SQL执行效率的16种方法
    Spring Ioc DI 原理
    java内存泄漏
    转:js闭包
    LeetCode Best Time to Buy and Sell Stock III
    LeetCode Best Time to Buy and Sell Stock with Cooldown
    LeetCode Length of Longest Fibonacci Subsequence
    LeetCode Divisor Game
    LeetCode Sum of Even Numbers After Queries
  • 原文地址:https://www.cnblogs.com/-tang/p/13283216.html
Copyright © 2011-2022 走看看