CompletableFuture避坑指南
目录
多CompletableFuture依赖同步调用场景的执行顺序
多CompletableFuture依赖同步调用场景避坑指南
1.避免出现依赖异步RPC调用结果的情况
2.CompletableFuture任务一定要指定任务线程池
多CompletableFuture依赖同步调用场景的执行顺序
多个CompletableFuture之间使用如thenApply、thenAccept等无Async后缀的方法进行调用时,往往是为了基于被依赖任务的执行结果做逻辑处理。
先来看一下这种调用方式的执行顺序情况
- 如果依赖CompletableFuture执行时被依赖的CompletableFuture已经有了结果值则后者的执行由当前程序的主线程执行。
- 如果依赖CompletableFuture执行时被依赖的CompletableFuture还没有返回结果值,则依赖者逻辑的执行由被依赖者结果回调的线程来执行。
下面的例子分别反映了上面说的两种顺序。
结果1中主线程先输出了表示thenApply的逻辑是等待future返回结果后又执行的,此时thenApply逻辑的执行线程是Dubbo线程。
结果2则是先输出thenApply中信息后输出的主线程信息,thenApply中逻辑的执行线程是主线程
//dubbo客户端设置异步调用
@MReference(async = true)
private EOrderFacade eOrderFacade;@Test
public void test() throws InterruptedException {//dubbo客户端异步时同步返回结果为nullResult<OOrder> result = eOrderFacade.getOrder(GetOrderParam.builder().orderId(String.valueOf(8095950037325722953l)).build());CompletableFuture<Result<OOrder>> future = RpcContext.getContext().getCompletableFuture();//此时,如果rpc调用中的业务操作已经执行完毕并返回,则该thenApply中的逻辑直接由当前主线程执行;否则,将会在dubbo客户端的IO线程中的线程执行。future.thenApply(cf3Result -> {System.out.println("当前时间:"+ DateUtils.parseDateToStr(DateUtils.PATTERN_YYYY_MM_DD_HHMMSS,new Date())+" thenApply中的逻辑 执行线程名称:"+Thread.currentThread().getName());System.out.println("dubbo调用future的结果"+ JSON.toJSONString(cf3Result));return "【thenApply结果】";}).exceptionally(e -> {log.error("thenApply 执行异常:", e);return "【thenApply异常结果】";});System.out.println("程序主线程名称:"+Thread.currentThread().getName());Thread.sleep(10000);
}//输出结果 1
程序主线程名称:main
当前时间:2023-03-25 17:55:47 thenApply中的逻辑 执行线程名称:DubboClientHandler-172.19.1.164:20880-thread-1
dubbo调用future的结果{"data":{"activeAt":1679652553000,"activityTotal":-0.01,"additionServicePrice":0.0,"address":"订单完结立即隐藏顾客地址"},"status":0,"success":true}//输出结果 2
当前时间:2023-03-25 17:47:23 thenApply中的逻辑 执行线程名称:main
dubbo调用future的结果{"data":{"activeAt":1679652553000,"activityTotal":-0.01,"additionServicePrice":0.0,"address":"订单完结立即隐藏顾客地址"},"status":0,"success":true}
程序主线程名称:main
多CompletableFuture依赖同步调用场景避坑指南
如果被依赖任务执行时间较长就会出现上面说的第二种执行顺序就可能会踩坑,下面是几点需要注意的场景
1.避免出现依赖异步RPC调用结果的情况
如果被依赖的是RPC异步调用,则依赖者的结果回调线程是RPC服务的IO线程池,以Dubbo为例,此时程序中依赖任务的执行线程就变成了由Dubbo的IO线程,而服务中只会有一个IO线程池,所以需要注意同步回调中不能有阻塞等耗时过长的逻辑,否则在这些逻辑执行完成前,IO线程将一直被占用,影响整个服务的响应。
业务逻辑交给Dubbo的IO线程池来执行就很危险了,会严重影响Dubbo的性能,如果逻辑耗时较高后果更严重;因此在使用过程中要避免这种场景的使用。
2.CompletableFuture任务一定要指定任务线程池
如果任务没有指定线程池,默认是由系统ForkJoinPool中的共用线程池CommonPool执行的,那依赖这个任务的结果的任务也会由CommonPool来执行。
CommonPool线程池的大小是CPU核数-1,如果依赖任务中存在耗时较长的逻辑就可能导致本就不富裕的CommonPool线程池阻塞,带来性能瓶颈甚至生产事故。