前言:要写一手优雅的代码,背后要经历很多的积累和沉淀,有点类似台上一分钟,台下十年功.要让代码变得优雅有很多种方式,使用JDK8提供的新特性便是其中一种,虽然相较于设计模式对代码质量提升有限,但仍值得去学习和使用.本篇仅介CompletableFuture.
早在JDK1.5中就提供了Future和Callable来获取异步任务的结果,但因为获取结果阻塞的原因,并没有真正实现异步带来的价值.体验非常不好,于是JDK在1.8中终于出了Future的增强版CompletableFuture,借助CompletableFuture提供的异步能力和lambda表达式风格,可以写出优雅的异步编程代码.
目录
1.创建CompletableFuture对象
CompletableFuture提供了4个静态方法用于创建CompletableFuture实例:
CompletableFuture<Void> runAsync(Runnable runnable)
CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
CompletableFuture<U> supplyAsync(Supplier<U> supplier)
CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
其中带Executor参数的用于指定自定义异步执行线程池,如果不指定则默认使用JDK提供的FrokJoinPool.commonPool().
如果无需关心执行后的结果,使用runAsync创建即可,否则使用supplyAsync创建CompletableFuture.
2.同步获取计算结果
CompletableFuture提供4种用于同步等待获取计算结果的方法,这点跟老的Future没啥两样,只不过额外提供了getNow和join方法.
public T get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent)
public T join()
getNow用于立即获取返回结果,如果此时被调用方法还没返回值,则返回指定的valueIfAbsent.
join方法和get作用一样,用于阻塞获取执行结果,不同的是join在调用方法出现异常时仅会返回uncheckedException,而get则返回具体异常.
3.计算完成后的处理
CompletableFuture可以通过whenComplete/whenCompleteAsync来获取计算结果并处理,不再需要阻塞获取,而是以异步通知回调的方式.
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
4.处理多阶段任务
如果一个任务需要多个阶段才能最终完成,那么可以采用thenApply这种方式链式调用完成整个任务.
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
也可以采用handle方式:
public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
与thenApply不同的是,handle是在任务完成后再执行,且可以处理异常,thenApply遇到异常则会抛出.
如果任务无需返回,只需要处理结果,那么可以使用消费者
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
5.组合任务
thenAcceptBoth用于组合两个任务,当两个任务都计算完成时执行:
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,
BiConsumer<? super T, ? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
BiConsumer<? super T, ? super U> action)
thenAcceptEither只要两个任务中有一个先完成了即执行:
public <U> CompletableFuture<U> applyToEither(
CompletionStage<? extends T> other, Function<? super T, U> fn)
如果要组合的任务超过2个,可以用:
任意一个完成:
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
全部完成:
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
6.Tips
JDK8CompletableFuture API中提供了五十多个静态方法,乍看上去很多,其实实际开发中经常被用到的连一半都不到,再加上大部分其实是方法重载,以及功能类似方法,抽象一下最后只需要掌握十个以内的方法,便可以驾驭CompletableFuture写出优雅代码.
创建CompletableFuture对象时,以run打头的方法无返回值,以supply打头的方法有返回,可以对返回结果进一步处理.
处理任务时,方法名中包含Apply的方法有返回值,是生产者,方法名中包含Accept的方法无返回值,是消费者.
以Async结尾的方法表明该方法异步执行.
7.代码演示
上面都是纯纯的理论部分,并没有结合实际代码演示,所以下面正式结合实际代码演示之:
几种经常会用到场景如下所示,如果不需要处理返回结果,可以将Apply替换为Accept作为纯消费者即可
public class Client {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//case1:任务需要多个阶段才能完成,完成执行后续动作...
CompletableFuture.supplyAsync(() -> "hello")
.thenApplyAsync(s -> s + " ")
.thenApplyAsync(s -> s + "world")
.thenApplyAsync(String::toUpperCase)
.whenCompleteAsync((result, throwable) -> System.out.println(result));
//case2:完成任务需要调用rpc接口,有很多个rpc接口,只要其中一个调用成功即算完成任务
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
//模拟调用延迟
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "world";
});
//省略其它rpc接口...
CompletableFuture<Object> res = CompletableFuture.anyOf(future1, future2);
System.out.println(res.get());
//case3:完成任务需要所有的rpc接口都完成才算完成
//如果使用allOf是没有返回结果的
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
System.out.println(all.get());
//所以如果需要获取最终返回的结果,需要手动处理异步返回的结果
String combine = Stream.of(future1, future2)
.map(CompletableFuture::join)
.collect(Collectors.joining());
System.out.println(combine);
}
}
运行结果:
8.总结
CompletableFuture封装的真的好,以至于我在写上面这段代码时都不想写下去了,真的太简单了,没啥好演示的,只要有jdk8的lambda基础和functional接口基础,就可以轻松上手,之所以写这篇也是为了做个笔记,总结下然后加深印象,感谢你的阅读,文中若有不正之处,欢迎留言斧正。