本文介绍了CompletableFuture并行开发的基本原理,并提供了一些实践案例,希望对大家理解并行开发提供一些帮助。
问题背景
- APP首页作为前台核心导购流程,需要将 【附近门店】,【推荐商品】,【banner图】,【活动页】等多个中台能力聚合成更具象化的业务流程;
- 但是各个中台的能力接口性能不尽相同(因为中台不关心业务):比如附近门店接口99线接近300ms,推荐商品99线接近200ms
- 基于以上2点,业务研发就面临了一个核心问题:如果将多个性能参差不齐的中台接口聚合成用户体验好,性能高的业务流程
核心接口依赖分析
功能 | 依赖接口 | 平响(ms) | 99线(ms) | 备注 |
---|---|---|---|---|
附近门店 | shop/nearbyStore | 66 | 101 | 根据经纬度获取附近门店 |
推荐商品 | product/getRecommendCommodities | 170 | 260 | 获取销量前五的商品 |
活动信息 | activity/activityInformation | 62 | 121 | 获取配置的活动 |
banner图 | operation/getBanner | 24 | 43 | 获取APP Banner |
如大家所见,如果要渲染出所有的信息,则平响至少需要322ms; 99线至少需要:525ms,作为APP核心入口之一,这样的性能显然是不过关的。
如何解决性能问题
寻找解决方案
面对该问题时尝试做了以下推演:
- 敦促下游依赖提升服务性能:
- 将页面所需数据进行分批下发,进行异步渲染:
推演方向 | 可行性分析 | 最终结论 |
---|---|---|
敦促下游依赖提升服务性能 | – 可行性低 – 投入产出比低 | 无效推演 |
将页面所需数据进行分批下发,进行异步渲染 | 核心数据均在首屏加载完毕 | 无效推演 |
异步加载数据 | 业务流程中存在较多无直接依赖数据可尝试并行加载 | 有效推演 |
并行加载技术方案的选取
技术方向 | 优点 | 缺点 | 最终选择 |
---|---|---|---|
Future | – JDK自带 – 简单易上手 | 只能通过阻塞或者轮询方式获取返回值 | 淘汰 |
CompletableFuture | – JDK自带 – 简单易上手 – 异步任务结束或者出错时,会自动回调某个方法 – 主线程设置回调后,无需关心异步任务的执行 | 入选 | |
RxJava | – 学习成本高 – 公司内部暂未推广使用 | 淘汰 | |
Reactor | – 学习成本高 – 公司内部暂未推广使用 | 淘汰 |
CompletableFuture 异步加载的实现
CompletableFuture通过异步回调的方式,解决了开发过程中异步调用获取结果的难点。开发人员只需接触到CompletableFuture对象,以及CompletableFuture任务的执行结果,无需设计具体异步回调的实现,并可通过自定义线程池进一步优化任务的异步调用。
CompletableFuture 的实例化
CompletableFuture提供了四个静态方法来创建一个实例
构造方法说明
方法声明 | 说明 |
---|---|
public static CompletableFuture<Void> runAsync(Runnable runnable) | – 无需返回结果的异步调用 – 使用默认的线程池 ForkJoinPool.commonPool() |
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) | – 无需返回结果的异步调用 – 使用用户自定义的线程池 |
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) | – 需要返回值的异步调用 – 使用默认的线程池 ForkJoinPool.commonPool() |
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) | – 需要返回值的异步调用 – 使用用户自定义的线程池 |
代码示例
- 无返回值且未指定线程池的
public static void main(String[] args) {
CompletableFuture<Void> result = CompletableFuture.runAsync(
() -> testNoResult("JF") );
int age = 11;
System.out.println("JF今年的年纪是" + age);
}
public static void testNoResult(String name) {
System.out.println(name +" 调用了 没有返回值的异步方法");
}
- 无返回值且指定了线程池
public static void main(String[] args) {
TaskThreadPoolExecutor threads = new TaskThreadPoolExecutor(20, 100, 30,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(500));
CompletableFuture<Void> result = CompletableFuture.runAsync(
() -> testNoResult("JF"), threads);
}
public static void testNoResult(String name) {
System.out.println(name +" 调用了 没有返回值,但是制定了线程池的的异步方法");
}
- 有返回值且未指定线程池的
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<String> result = CompletableFuture.supplyAsync(
() -> testNoResult("JF"));
String resultDesc = result.get(1000, TimeUnit.MILLISECONDS);
System.out.println(resultDesc);
}
public static String testNoResult(String name) {
return (name +" 调用了 没有返回值,但是制定了线程池的的异步方法");
}
- 有返回值且指定线程池
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
TaskThreadPoolExecutor threads = new TaskThreadPoolExecutor(20, 100, 30,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(500));
CompletableFuture<String> result = CompletableFuture.supplyAsync(
() -> testNoResult("JF"), threads);
String resultDesc = result.get(1000, TimeUnit.MILLISECONDS);
System.out.println(resultDesc);
}
public static String testNoResult(String name) {
return (name +" 调用了 没有返回值,但是制定了线程池的的异步方法");
}
获取任务执行执行结果
方法声明 | 说明 |
---|---|
public T get(); | – 实现了Future接口的功能 – 会一直阻塞直到获取线程结果 |
public T get(long timeout, TimeUnit unit); | – 实现了Future接口的功能 – 指定时间内仍未获取到结果会抛出异常 |
public T getNow(T valueIfAbsent); | – 不会产生阻塞 – 如果任务未执行完成返回默认缺省值 |
public T join(); | 一直阻塞直到获取线程结果 – 唯一不同就是 join 方法不会抛出检查时异常 |
触发任务完成
方法声明 | 说明 |
---|---|
public boolean complete(T value); | – 主动触发当前异步任务的完成,如果已完成,该方法返回false – 如果未完成则返回true,且其他线程将传入的 T value 当做返回值 |
public boolean completeExceptionally(Throwable ex); | 触发任务执行的异常 |
complete
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
TaskThreadPoolExecutor threads = new TaskThreadPoolExecutor(20, 100, 30,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(500));
CompletableFuture<String> result = CompletableFuture.supplyAsync(
() -> testNoResult("JF"), threads);
boolean complete = result.complete("Ascend 来插队了");
System.out.println("并行是否完成?" + !complete +" 结果是 " + result.get());
}
public static String testNoResult(String name) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return "nothing";
}
return (name +" 调用了");
}
===================================================================================
输出结果
并行是否完成?false 结果是 Ascend 来插队了
completeExceptionally
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
TaskThreadPoolExecutor threads = new TaskThreadPoolExecutor(20, 100, 30,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(500));
CompletableFuture<String> result = CompletableFuture.supplyAsync(
() -> testNoResult("JF"), threads);
BizException bizException = new BizException(BizErrorCodeEnum.PARAM_ERROR);
boolean complete = result.completeExceptionally(bizException);
System.out.println("并行是否完成?" + !complete +" 结果是 " + result.get());
}
public static String testNoResult(String name) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return "nothing";
}
return (name +" 调用了");
}
===================================================================================
输出结果
Exception in thread "main" java.util.concurrent.ExecutionException: com.tuhu.boot.common.exceptions.BizException: 参数错误
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908)
at com.tuhu.cl.install.agg.service.common.TaskConfig.main(TaskConfig.java:49)
Caused by: com.tuhu.boot.common.exceptions.BizException: 参数错误
at com.tuhu.cl.install.agg.service.common.TaskConfig.main(TaskConfig.java:47)
对任务执行结果进行下一步处理
只能接受任务正常执行后的回调
方法声明 | 说明 | 代码示例 | 执行结果 |
---|---|---|---|
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn); | 拿到上一步任务执行的结果进行处理并且返回处理的结果 | | 上一步的执行的结果为:10 |
public CompletableFuture<Void> thenRun(Runnable action); | 拿不到上一步任务执行的结果,但会执行Runnable接口的实现 | | 上一步执行完成 |
public CompletionStage<Void> thenAccept(Consumer<? super T> action); | 可以拿到上一步任务执行的结果进行处理,但不需要返回处理的结果 | | 上一步执行完成,结果为:10 |
只能接收任务处理异常后的回调
方法声明 | 说明 | 代码示例 | 执行结果 |
---|---|---|---|
public CompletionStage<T> exceptionally( Function<Throwable, ? extends T> fn); | – 任务执行过程中出现异常的时候,会回调exceptionally方法 – exceptionally能够将异常给吞了,并且fn的返回值会返回回去(有点降级的味道了) | | 出现异常了,返回默认值 100 |
能同时接收任务执行正常和异常的回调
方法声明 | 说明 | 代码示例 | 执行结果 |
---|---|---|---|
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn); | – 跟exceptionally有点像,但是exceptionally是出现异常才会回调,handle正常情况下也能回调 – 两者都有默认返回值 | ||
public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> actin); | – 能接受正常或者异常的回调,并且不影响上个阶段的返回值,也就是主线程能获取到上个阶段的返回值 – 当出现异常时,无降级操作,主线程在获取执行异常任务的结果时,会抛出异常。 |
| whenComplete Exception: |
结果合并
方法声明 | 说明 |
---|---|
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn); | 组合两个 future,获取两个 future 的返回结果,并返回当前任务的返回值 |
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) | 组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值 |
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action) | 组合两个 future,不需要获取 future 的结果,只需两个 future 处理完任务后,处理该任务 |
应用实践
代码示例
@Resource("appHomeExecutor")
private Executor executor;
@Override
public void initApp() {
// 1:异步查询附近门店
CompletableFuture<ShopInfoVo> shopCompletableFuture = CompletableFuture.supplyAsync(() -> {
return new ShopInfoVo();
}, executor);
// 2:异步查询推荐商品
CompletableFuture<ProductVo> productCompletableFuture = CompletableFuture.supplyAsync(() -> {
return new ProductVo();
}, executor);
// 3:异步查询活动信息
CompletableFuture<ActivityVo> activityVoCompletableFuture = CompletableFuture.supplyAsync(() -> {
return new ActivityVo();
}, executor);
// 4:异步查询banner图
CompletableFuture<BannerVo> bannerCompletableFuture = CompletableFuture.supplyAsync(() -> {
return new BannerVo();
}, executor);
// 等待所有异步运算完成
CompletableFuture.allOf(shopCompletableFuture, productCompletableFuture, activityVoCompletableFuture,
bannerCompletableFuture);
try {
// 获取异步运算结果
ShopInfoVo shopInfoVo = shopCompletableFuture.get();
ProductVo productVo = productCompletableFuture.get();
ActivityVo activityVo = activityVoCompletableFuture.get();
BannerVo bannerVo = bannerCompletableFuture.get();
// 组装响应数据
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
实际效果
性能指标 | 平响 | 95线 | 99线 |
---|---|---|---|
响应时间 | 132 | 162 | 174 |