业务场景
在很多项目中,都有类似数据汇总的业务场景,查询今日注册会员数,在线会员数,订单总金额,支出总金额等。。。这些业务通常都不是存在同一张表中,我们需要依次查询出来然后封装成所需要的对象返回给前端。那么在此过程中,就可以把这个接口中“大任务”拆分成 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.Testpublic 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.以上这种方式只能用在没有事务的业务中,因为在多线程中,事务是不共享的。
评论