public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("执行过程=="+Thread.currentThread().getName());
try {
Thread.sleep(3000);
return "123";
} catch (Exception e) {
e.printStackTrace();
return "456";
}
}).thenApply(
result ->{
System.out.println("thenApply"+Thread.currentThread().getName());
System.out.println(result);
return "";
}).exceptionally(err ->{
System.out.println("发生异常");
return "";
});
System.out.println("获取结果=="+Thread.currentThread().getName());
}
}
如上代码,
模拟先异步调用一个接口,然后后边的调用步骤依赖前面一个接口的返回值,如果不加sleep,那后边thenapply里的调用是main主线程去调用的,会立马返回 日志也会打印,因为主线程的执行过程完整记录下来了,但是如果加了sleep,也就是第一个接口调用的耗时比较长,下一个接口等待就会阻塞,completeableFuture会启用异步线程(如果加了线程池会使用自定义的线程池,没添加的话使用forkjoin线程池),需要注意的是
控制台的日志不会打印第二个接口调用打印的"thenApply",因为控制台打印的是主线程的日志,主线程因为调用完第一个接口立马结束不等待返回值,所以生命周期较短,而异步回调线程在等待3秒后回来打印的时候,主线程已经结束,控制台看不到异步线程的日志了。
结论:
线程执行问题
CompletableFuture实现了CompletionStage接口,通过丰富的回调方法,支持各种组合操作,每种组合场景都有同步和异步两种方法。
同步方法(即不带Async后缀的方法)有两种情况。
如果注册时被依赖的操作已经执行完成,则直接由当前线程执行
如果注册时被依赖的操作还未执行完,则由回调线程执行
异步方法(即带Async后缀的方法):可以选择是否传递线程池参数Executor运行在指定线程池中;当不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPool(CommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈)。
建议强制传线程池,且根据实际情况做线程池隔离。
如果注册时被依赖的操作已经执行完成,则直接由当前线程执行
如果注册时被依赖的操作还未执行完,则由回调线程执行
以上两点可以保证在调用多个步骤的方法时能够快速结束调用,如果第一步结束了那就由主线程调用,如果没结束会等到异步线程回调来了之后去调用,所以不会阻塞主线程的继续执行。