在JDK1.5已经提供了Future和Callable的实现,可以用于阻塞式获取结果,如果想要异步获取结果,通常都会以轮询的方式去获取结果,如下:
//定义一个异步任务
Future<String> future = executor.submit(()->{
Thread.sleep(2000);
return "hello world";
});
//轮询获取结果
while (true){
if(future.isDone()) {
System.out.println(future.get());
break;
}
}
从上面的形式看来轮询的方式会耗费无谓的CPU资源,而且也不能及时地得到计算结果.所以要实现真正的异步,上述这样是完全不够的,在Netty中,我们随处可见异步编程
ChannelFuture f = serverBootstrap.bind(port).sync();
f.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
System.out.println("complete");
}
});
而JDK1.8中的CompletableFuture就爲我们提供了异步函数式编程,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的複杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
-
创建CompletableFuture对象
CompletableFuture提供了四个静态方法用来创建CompletableFuture对象:
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
Asynsc表示异步,而supplyAsync与runAsync不同在与前者异步返回一个结果,后者是void.第二个函数第二个参数表示是用我们自己创建的线程池,否则採用默认的ForkJoinPool.commonPool()作爲它的线程池.其中Supplier是一个函数式接口,代表是一个生成者的意思,传入0个参数,返回一个结果.(更详细的可以看我另一篇文章)
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
return "hello world";
});
System.out.println(future.get()); //阻塞的获取结果 ''helllo world"
-
主动计算
以下4个方法用于获取结果
同步获取结果
public T get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent)
public T join()
getNow有点特殊,如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent值。join()与get()区别在于join()返回计算的结果或者抛出一个unchecked异常(CompletionException),而get()返回一个具体的异常.
主动触发计算
public boolean complete(T value)
public boolean completeExceptionally(Throwable ex)
上面方法表示当调用CompletableFuture.get()被阻塞的时候,那麽这个方法就是结束阻塞,并且get()获取设置的value.
public static CompletableFuture<Integer> compute() {
final CompletableFuture<Integer> future = new CompletableFuture<>();
return future;
}
public static void main(String[] args) throws Exception {
final CompletableFuture<Integer> f = compute();
class Client extends Thread {
CompletableFuture<Integer> f;
Client(String threadName, CompletableFuture<Integer> f) {
super(threadName);
this.f = f;
}
@Override
public void run() {
try {
System.out.println(this.getName() + ": " + f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
new Client("Client1", f).start();
new Client("Client2", f).start();
System.out.println("waiting");
//设置Future.get()获取到的值
f.complete(100);
//以异常的形式触发计算
//f.completeExceptionally(new Exception());
Thread.sleep(1000);
}
-
计算结果完成时的处理
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个方法是当计算阶段结束的时候触发,BiConsumer有两个入参,分别代表计算返回值,另外一个是异常.无返回值.方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
future.whenCompleteAsync((v,e)->{
System.out.println("return value:"+v+" exception:"+e);
});
-
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)
与whenComplete()不同的是这个函数返回CompletableFuture并不是原始的CompletableFuture返回的值,而是BiFunction返回的值.
-
CompletableFuture的组合
-
thenApply
当计算结算完成之后,后面可以接继续一系列的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方法的区别在于handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。而thenApply方法只是用来处理正常值,因此一旦有异常就会抛出。
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
return "hello world";
});
CompletableFuture<String> future3 = future.thenApply((element)->{
return element+" addPart";
}).thenApply((element)->{
return element+" addTwoPart";
});
System.out.println(future3.get());//hello world addPart addTwoPart
-
CompletableFuture的Consumer
只对CompletableFuture的结果进行消费,无返回值,也就是最后的CompletableFuture是void.
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)
//入参爲原始的CompletableFuture的结果.
future4.get();
thenAcceptBoth
这个方法用来组合两个CompletableFuture,其中一个CompletableFuture等待另一个CompletableFuture的结果.
```
CompletableFuture future4 = future.thenAccept((e)->{
System.out.println("without return value");
});
```
future4.get();
-
thenAcceptBoth
这个方法用来组合两个CompletableFuture,其中一个CompletableFuture等待另一个CompletableFuture的结果.
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
return "hello world";
});
CompletableFuture future5 = future.thenAcceptBoth(CompletableFuture.completedFuture("compose"),
(x, y) -> System.out.println(x+y));//hello world compose
-
Either和ALL
thenAcceptBoth是当两个CompletableFuture都计算完成,而我们下面要了解的方法applyToEither是当任意一个CompletableFuture计算完成的时候就会执行。
Random rand = new Random();
CompletableFuture<Integer> future9 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
CompletableFuture<Integer> future10 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 200;
});
//两个中任意一个计算完成,那麽触发Runnable的执行
CompletableFuture<String> f = future10.applyToEither(future9,i -> i.toString());
//两个都计算完成,那麽触发Runnable的执行
CompletableFuture f1 = future10.acceptEither(future9,(e)->{
System.out.println(e);
});
System.out.println(f.get());
如果想组合超过2个以上的CompletableFuture,allOf和anyOf可能会满足你的要求.allOf方法是当所有的CompletableFuture都执行完后执行计算。anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。
总结
有了CompletableFuture之后,我们自己实现异步编程变得轻鬆很多,这个类也提供了许多方法来组合CompletableFuture.结合Lambada表达式来用,变得很轻鬆.
网友评论