CompletableFuture

CompletableFuture是java8中添加的一个类了,这个类主要的作用就是提供了新的方式来完成异步处理,包括合成和组合事件的非阻塞方式。

简单示例:
对于Future可以这样提交任务:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. ExecutorService executor = Executors.newFixedThreadPool(5);  
  2.   
  3. Future<String> result = executor.submit(() -> {  
  4.     TimeUnit.SECONDS.sleep(3);  
  5.     return "hello";  
  6. });  
  7. System.out.println(result.get());  
使用CompletableFuture:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<String> resultCompletableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {  
  2.     @Override  
  3.     public String get() {  
  4.         try {  
  5.             TimeUnit.SECONDS.sleep(3);  
  6.         } catch (InterruptedException e) {  
  7.             // TODO Auto-generated catch block  
  8.             e.printStackTrace();  
  9.         }  
  10.         return "hello";  
  11.     }  
  12. }, executor);  
  13. System.out.println(resultCompletableFuture.get());  
这里,CompletableFuture和Future的get()调用都会阻塞,但是可以CompletableFuture注册类似一个回调函数去处理结果:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<String> resultCompletableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {  
  2.     @Override  
  3.     public String get() {  
  4.     try {  
  5.         TimeUnit.SECONDS.sleep(1);  
  6.         System.out.println(Thread.currentThread().getName());  
  7.     } catch (InterruptedException e) {  
  8.        e.printStackTrace();  
  9.     }  
  10.     return "hello";  
  11.     }  
  12. }, executor);  
  13. System.out.println(resultCompletableFuture.thenAccept(new Consumer<String>(){  
  14.     @Override  
  15.     public void accept(String t) {  
  16.     System.out.println(t);  
  17.     System.out.println(Thread.currentThread().getName());  
  18.     }  
  19. }));  
  20. System.out.println(123);  
运行结果:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. java.util.concurrent.CompletableFuture@1b28cdfa[Not completed]  
  2. 123  
  3. pool-1-thread-2  
  4. hello  
  5. pool-1-thread-2  
可以看出来,在CompletableFuture注册的回调函数的执行与其提交的任务的执行是同一个线程完成的。如果不想同一个线程来完成回调函数,可以这样:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<String> resultCompletableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {  
  2.     @Override  
  3.     public String get() {  
  4.     try {  
  5.         TimeUnit.SECONDS.sleep(1);  
  6.         System.out.println(Thread.currentThread().getName());  
  7.     } catch (InterruptedException e) {  
  8.         // TODO Auto-generated catch block  
  9.         e.printStackTrace();  
  10.     }  
  11.     return "hello";  
  12.     }  
  13. }, executor);  
  14. resultCompletableFuture.thenAcceptAsync(new Consumer<String>() {  
  15.     @Override  
  16.     public void accept(String t) {  
  17.     System.out.println(t);  
  18.     System.out.println(Thread.currentThread().getName());  
  19.     }  
  20. }, executor);  
  21. System.out.println(123);  
这里第二个参数executor也可以使用其它的线程池,当然如果不指定第二个参数,即
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. resultCompletableFuture.thenAcceptAsync(new Consumer<String>() {  
  2.             @Override  
  3.             public void accept(String t) {  
  4.                 System.out.println(t);  
  5.                 System.out.println(Thread.currentThread().getName());  
  6.             }  
  7.         });  
这时会使用ForkJoinPool.commonPool这个线程池。

可以对通过completeExceptionally 函数对CompletableFuture发出异常通知:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<String> resultCompletableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {  
  2.     @Override  
  3.     public String get() {  
  4.     try {  
  5.         TimeUnit.SECONDS.sleep(3);  
  6.         System.out.println("run--" + Thread.currentThread().getName());  
  7.     } catch (InterruptedException e) {  
  8.         e.printStackTrace();  
  9.     }  
  10.     return "hello";  
  11.     }  
  12. }, executor);  
  13. resultCompletableFuture.thenAccept(new Consumer<String>() {  
  14.     @Override  
  15.     public void accept(String t) {  
  16.     System.out.println("accept--" + t);  
  17.     System.out.println(Thread.currentThread().getName());  
  18.     }  
  19. });  
  20. resultCompletableFuture.completeExceptionally(new Exception("error"));  
  21. System.out.println("over");  
运行结果:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. over  
  2. run--pool-1-thread-1  
可以发现thenAccept注册的回调函数不再执行,但是任务还是会执行完。
可以显式声明来处理异常:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<String> resultCompletableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {  
  2.     @Override  
  3.     public String get() {  
  4.     try {  
  5.         TimeUnit.SECONDS.sleep(3);  
  6.         System.out.println("run--" + Thread.currentThread().getName());  
  7.     } catch (InterruptedException e) {  
  8.         e.printStackTrace();  
  9.     }  
  10.     return "hello";  
  11.     }  
  12. }, executor);  
  13. resultCompletableFuture.thenAccept(new Consumer<String>() {  
  14.     @Override  
  15.     public void accept(String t) {  
  16.     System.out.println("accept--" + t);  
  17.     System.out.println(Thread.currentThread().getName());  
  18.     }  
  19. }).exceptionally(new Function<Throwable, Void>(){  
  20.     @Override  
  21.     public Void apply(Throwable t) {  
  22.     System.out.println(t.getMessage());  
  23.     return null;  
  24.     }  
  25. });  
  26. resultCompletableFuture.completeExceptionally(new Exception("error"));  
  27. System.out.println("over");  
运行结果:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. java.lang.Exception: error  
  2. over  
  3. run--pool-1-thread-1  
但是,如果exceptionally的声明提前了呢?
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<String> resultCompletableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {  
  2.     @Override  
  3.     public String get() {  
  4.     try {  
  5.         TimeUnit.SECONDS.sleep(3);  
  6.         System.out.println("run--" + Thread.currentThread().getName());  
  7.     } catch (InterruptedException e) {  
  8.         e.printStackTrace();  
  9.     }  
  10.     return "hello";  
  11.     }  
  12. }, executor);  
  13. resultCompletableFuture.exceptionally(new Function<Throwable, String>() {  
  14.     @Override  
  15.     public String apply(Throwable t) {  
  16.     System.out.println(t.getMessage());  
  17.     return t.getMessage();  
  18.     }  
  19. }).thenAccept(new Consumer<String>() {  
  20.     @Override  
  21.     public void accept(String t) {  
  22.     System.out.println("accept--" + t);  
  23.     System.out.println("accept--" + Thread.currentThread().getName());  
  24.     }  
  25. });  
  26. resultCompletableFuture.completeExceptionally(new Exception("error"));  
  27. System.out.println("over");  
注意到,两次exceptionally的参数Function <T, R>的类型值是不一样的,运行结果:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. error  
  2. accept--error  
  3. accept--main  
  4. over  
  5. run--pool-1-thread-1  

可以看出,在thenAccept前声明的exceptionally会有一个返回值传递给thenAccept。

创造和获取CompletableFuture

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);  
  2. static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);  
  3. static CompletableFuture<Void> runAsync(Runnable runnable);  
  4. static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);  

转换和作用于CompletableFuture(thenApply)

CompletableFuture优于Future,因为CompletableFuture是一个原子也是一个因子。Scala和JavaScript都允许future完成时允许注册异步回调,直到它准备好才要等待和阻止它。可以简单地说:运行这个函数时就出现了结果。此外,可以叠加这些功能,把多个future通过thenApply()组合在一起:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. ExecutorService executor = Executors.newFixedThreadPool(5);  
  2. CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {  
  3.     return "zero";  
  4. }, executor);  
  5.   
  6. CompletableFuture<Integer> f2 = f1.thenApply(new Function<String, Integer>() {  
  7.   
  8.     @Override  
  9.     public Integer apply(String t) {  
  10.         System.out.println(2);  
  11.         return Integer.valueOf(t.length());  
  12.     }  
  13. });  
  14.   
  15. CompletableFuture<Double> f3 = f2.thenApply(r -> r * 2.0);  
  16. System.out.println(f3.get());  
运行结果:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. zero  
  2. 8.0  
注意,不是以Async结尾的方法将在future完成的相同线程中调用该方法中的参数,而以Async结尾的方法将在不同的线程池中异步地调用方法中的参数。

运行完成的代码(thenAccept/thenRun)

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<Void> thenAccept(Consumer<? super T> block);  
  2. CompletableFuture<Void> thenRun(Runnable action);  
在future的管道里有两种典型的“最终”阶段方法,可以理解为回调函数。…Async变量也可用两种方法,隐式和显式执行器,thenAccept()/thenRun()方法并没有发生阻塞(即使没有明确的executor)。它们像一个事件侦听器/处理程序。

单个CompletableFuture的错误处理

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<String> safe = future.exceptionally(ex -> "We have a problem: " + ex.getMessage());  
exceptionally()接受一个函数时,将调用原始future来抛出一个异常。这里会有机会将此异常转换为和Future类型的兼容的一些值来进行恢复。safe进一步的转换将不再产生一个异常而是从提供功能的函数返回一个String值。一个更加灵活的方法是handle()接受一个函数,它接收正确的结果或异常:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<Double> future = f2.thenApply(r -> r * 2.0);  
  2. future.handle(new BiFunction<Double, Throwable, Double>(){  
  3.     @Override  
  4.     public Double apply(Double t, Throwable u) {  
  5.     if (t != null) {  
  6.         System.out.println("handler");  
  7.         return t;  
  8.     }else {  
  9.         System.out.println(u);  
  10.         return -1.0;  
  11.     }  
  12.     }  
  13. }  
handle()总是被调用,结果和异常都非空,这是个一站式全方位的策略。

结合(链接)两个futures(thenCompose())

有时想运行一些future的值(当它准备好了),但这个函数也返回了future。CompletableFuture足够灵活地明白函数结果现在应该作为顶级的future,对比
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<U> thenCompose(Function<? super T, Function<? super T, ? extends CompletionStage<U>> fn);//(CompletableFuture extends CompletionStage)  
  2.     public static void f5() throws InterruptedException, ExecutionException {  
  3.         ExecutorService executor = Executors.newFixedThreadPool(5);  
  4.         CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {  
  5.             return "zero";  
  6.         }, executor);  
  7.      CompletableFuture<CompletableFuture<String>> f4 = f1.thenApply(CompletableFutureTest::calculate);  
  8.      System.out.println(f4.get().get());  
  9.   
  10.      CompletableFuture<String> f5 = f1.thenCompose(CompletableFutureTest::calculate);  
  11.      System.out.println(f5.get());   
  12.   
  13.         System.out.println(f1.get());  
  14.     }  
  15.   
  16.     public static CompletableFuture<String> calculate(String input) {  
  17.         ExecutorService executor = Executors.newFixedThreadPool(5);  
  18.         CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {  
  19.             System.out.println(input);  
  20.             return input + "---" + input.length();  
  21.         }, executor);  
  22.         return future;  
  23.     }  
仔细观察thenApply()(map)和thenCompose()(flatMap)的类型和差异.thenCompose()是一个重要的方法允许构建健壮的和异步的管道,没有阻塞和等待的中间步骤。

两个futures的转换值(thenCombine())

当thenCompose()用于链接一个future时依赖另一个thenCombine,当他们都完成之后就结合两个独立的futures:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public <U,V> CompletableFuture<V> thenCombine(  
  2.         CompletionStage<? extends U> other,  
  3.         BiFunction<? super T,? super U,? extends V> fn)   
假设有两个CompletableFuture,一个加载Customer另一个加载最近的Shop。他们彼此完全独立,但是当他们完成时,想要使用它们的值来计算Route。这是一个可剥夺的例子:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. CompletableFuture<Customer> customerFuture = loadCustomerDetails(123);  
  2. CompletableFuture<Shop> shopFuture = closestShop();  
  3. CompletableFuture<Route> routeFuture =  
  4. customerFuture.thenCombine(shopFuture, (cust, shop) -> findRoute(cust, shop));  
  5.   
  6. private Route findRoute(Customer customer, Shop shop) //...  
注意,在Java 8中可以用(cust, shop) -> findRoute(cust, shop)简单地代替this::findRoute方法的引用:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. customerFuture.thenCombine(shopFuture, this::findRoute);  
有customerFuture 和 shopFuture。那么routeFuture包装它们然后“等待”它们完成。当他们准备好了,它会运行提供的函数来结合所有的结果 (findRoute())。当两个基本的futures完成并且 findRoute()也完成时,这样routeFuture将会完成。
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. ExecutorService executor = Executors.newFixedThreadPool(5);  
  2. CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {  
  3.     return "zero";  
  4. }, executor);  
  5. CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {  
  6.     return "hello";  
  7. }, executor);  
  8.   
  9. CompletableFuture<String> reslutFuture =  
  10.         f1.thenCombine(f2, new BiFunction<String, String, String>() {  
  11.   
  12.             @Override  
  13.             public String apply(String t, String u) {  
  14.                 return t.concat(u);  
  15.             }  
  16.         });  
  17.   
  18. System.out.println(reslutFuture.get());//zerohello  

等待所有的 CompletableFutures 完成

如果不是产生新的CompletableFuture连接这两个结果,我们只是希望当完成时得到通知,我们可以使用 thenAcceptBoth()/runAfterBoth()系列的方法。它们的工作方式与thenAccept() 和 thenRun()类似,但是是等待两个futures而不是一个:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public <U> CompletableFuture<Void> thenAcceptBoth(  
  2.     CompletionStage<? extends U> other,          
  3.     BiConsumer<? super T, ? super U> action)  
  4. public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,   
  5.     Runnable action)  
示例:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. ExecutorService executor = Executors.newFixedThreadPool(5);  
  2. CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {  
  3.     try {  
  4.         TimeUnit.SECONDS.sleep(1);  
  5.     } catch (Exception e) {  
  6.         // TODO Auto-generated catch block  
  7.         e.printStackTrace();  
  8.     }  
  9.     return "zero";  
  10. }, executor);  
  11. CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {  
  12.     try {  
  13.         TimeUnit.SECONDS.sleep(3);  
  14.     } catch (Exception e) {  
  15.         // TODO Auto-generated catch block  
  16.         e.printStackTrace();  
  17.     }  
  18.     return "hello";  
  19. }, executor);  
  20.   
  21. CompletableFuture<Void> reslutFuture = f1.thenAcceptBoth(f2, new BiConsumer<String, String>() {  
  22.     @Override  
  23.     public void accept(String t, String u) {  
  24.         System.out.println(t + " over");  
  25.         System.out.println(u + " over");  
  26.     }  
  27. });  
  28.   
  29. System.out.println(reslutFuture.get());  
运行结果:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. zero over  
  2. hello over  
  3. null  
好了,你当然可以这么做。但是最关键的一点是CompletableFuture是允许异步的,它是事件驱动的编程模型而不是阻塞并急切地等待着结果。所以在功能上,上面两部分代码是等价的,但后者没有必要占用一个线程来执行。

等待第一个 CompletableFuture 来完成任务

另一个有趣的事是CompletableFutureAPI可以等待第一个(与所有相反)完成的future。当有两个相同类型任务的结果时就显得非常方便,只要关心响应时间就行了,没有哪个任务是优先的。
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other,   
  2.                 Consumer<? super T> action)  
  3. public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,  
  4.                 Runnable action)  
作为一个例子,当有两个系统可以集成。一个具有较小的平均响应时间但是拥有高的标准差,另一个一般情况下较慢,但是更加容易预测。为了两全其美(性能和可预测性)可以在同一时间调用两个系统并等着谁先完成。通常这会是第一个系统,但是在进度变得缓慢时,第二个系统就可以在可接受的时间内完成:

完整地转换第一个系统

applyToEither()算是 acceptEither()的前辈了。当两个futures快要完成时,后者只是简单地调用一些代码片段,applyToEither()将会返回一个新的future。当这两个最初的futures完成时,新的future也会完成。
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other,   
  2.             Function<? super T, U> fn)  

多种结合的CompletableFuture

知道如何等待两个future来完成(使用thenCombine())并第一个完成(applyToEither())。但它可以扩展到任意数量的futures吗?的确,使用static辅助方法:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)  
  2. public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)  
allOf()当所有的潜在futures完成时,使用了一个futures数组并且返回一个future(等待所有的障碍)。另一方面 anyOf()将会等待最快的潜在futures。

可以对CompletableFuture指定处理完成的时间,如果按时完成则通知,否则抛出超时异常并处理。

[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public static void main(String[] args) {  
  2.     CompletableFuture<String> responseFuture = CompletableFuture.supplyAsync(() -> {  
  3.         try {  
  4.             TimeUnit.SECONDS.sleep(2);  
  5.         } catch (Exception e) {  
  6.             e.printStackTrace();  
  7.         }  
  8.         System.out.println("over");  
  9.         return "hello world!";  
  10.     });  
  11.     failAfter(Duration.ofSeconds(2)).acceptEither(responseFuture, (x) -> {  
  12.         System.out.println("responseFuture is over successed! the response is " + x);  
  13.   
  14.     }).exceptionally(throwable -> { //exceptionally必须在最后  
  15.         System.out.println("responseFuture is not over on time!");  
  16.         return null;  
  17.     });  
  18. }  
  19.   
  20. private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);  
  21. public static <T> CompletableFuture<T> failAfter(Duration duration) {  
  22.     final CompletableFuture<T> promise = new CompletableFuture<>();  
  23.     scheduler.schedule(() -> {  
  24.         final TimeoutException ex = new TimeoutException("Timeout after " + duration);  
  25.         return promise.completeExceptionally(ex);  
  26.     }, duration.toMillis(), TimeUnit.MILLISECONDS);  
  27.     return promise;  
  28. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值