概览
- CompletableFuture是java8引入的新类,该类实现了 Future 接口和 CompletionStage 接口,封装了future、forkjoin相关类来执行异步,所以你还是可以像以前一样通过阻塞(get)或者轮询的方式获得结果,尽管这种方式不推荐使用。
- CompletionStage 接口代表异步计算中的 不同阶段,以及如何 组合 这些计算阶段。
- CompletableStage 接口中有 50 多个方法,可以对 CompletableStage 进行组合、计算,方法看似很多,但可以按功能对其分类,大多数方法都有 3 种变体:
- 不带 Async 方法:同步方法
- 带 Async,只有一个参数:异步方法,使用默认的 ForkJoinPool.commonPool() 获取线程池
- 带 Async,有两个参数:异步方法,且使用第二个参数指定的 ExecutorService 线程池
了解CompletableFuture需要理解java8的函数式接口,不了解的同学可以先移步:https://my.oschina.net/u/3244997/blog/3014977
创建CompletableFuture对象
//比较特殊,他入参就是返回值,也就是说他可以用来执行需要其他返回值的异步任务。
public static CompletableFuture completedFuture(U value)
//无返回值,使用默认线程池
public static CompletableFuture runAsync(Runnable runnable)
//无返回值,使用自定义线程池
public static CompletableFuture runAsync(Runnable runnable, Executor executor)
//有返回值,使用默认线程池
public static CompletableFuture supplyAsync(Supplier supplier)
//有返回值,使用自定义线程池
public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)
//
public static CompletableFuture allOf(CompletableFuture>... cfs)
举例:
//supplyAsync方法无入参,但是返回一个String对象。此方法使用了默认的线程池执行异步任务
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
//长时间的计算任务
return "·00";
});
在创建完CompletableFuture对象并且执行任务之后,还可以对结果或者异常等进行额外的操作或任务
下面将通过具体例子来展示各个方法的使用。
1.whenComplete* 和 exceptionally 方法
当原始的CompletableFuture任务执行完后,不管是否成功计算出结果,还是抛出异常,都会会执行 whenComplete* 或 exceptionally 的方法中的任务。
该操作执行完毕后:
- 会返回一个新的CompletableFuture对象!!!
- 使用whenComplete*方法时,返回的新的CompletableFuture对象的返回结果和原始的CompletableFuture对象计算结果相同
- 使用 exceptionally方法时,如果原始计算逻辑抛出异常,那么返回的 新的CompletableFuture对象 的返回结果由该方法的return值决定;如果原始计算逻辑没有抛出异常,那么返回的 新的CompletableFuture对象 的返回结果和原始计算逻辑返回的结果一致。有点绕,先不明白没关系,下面会有四个exceptionally实例解释这段话。
BiConsumer 函数接口有两个参数,无返回值。
Function 函数接口有一个输入参数,返回一个结果。
//无Async,同步处理正常计算结果或异常,使用执行任务的那个线程来执行该方法,所以这个方法是同步的。
public CompletableFuture whenComplete(BiConsumer super T,? super Throwable> action)
//有Async,异步处理正常计算结果或异常,使用执行任务的那个线程池中的线程来执行该方法!所以这个方法是异步的。
public CompletableFuture whenCompleteAsync(BiConsumer super T,? super Throwable> action)
//有Async,异步处理正常计算结果或异常,使用自定义线程池来执行该方法,所以这个方法是异步的。
public CompletableFuture whenCompleteAsync(BiConsumer super T,? superThrowable> action, Executor executor)
//处理异常。
public CompletableFuture exceptionally(Function fn)
注意:当没有异常抛出来的时候,上面的Throwable参数为空!
举例:
计算逻辑:
private static Random random = new Random();
private static long time = System.currentTimeMillis();
public static int getMoreData(){
System.out.println("begin to start compute");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("end to compute, passed:" + System.currentTimeMillis());
return random.nextInt(1000);
}
public static int throwException(){
System.out.println("准备抛出异常");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("抛了");
throw new RuntimeException("主动抛出异常");
}
whenComplete:
//如果使用这段代码,则会是和当前线程同步执行
public static void main(String[] args) throws Exception{
CompletableFuture future = CompletableFuture.supplyAsync(() -> getMoreData());
CompletableFuture future2 = future.whenComplete((result, excetion) -> {
System.out.println("执行到whenComplete了,result:" + result);
System.out.println("执行到whenComplete了,exception:" + (excetion == null ? "无异常" : excetion.getClass()));
});
System.out.println("执行到最后一段代码了,future result:" + future.get());
System.out.println("执行到最后一段代码了,future2 result:" + future2.get());
}
> 打印执行结果:
begin to start compute
end to compute, passed:1551182552193
执行到whenComplete了,result:625
执行到whenComplete了,exception:无异常
执行到最后一段代码了,future result:625
执行到最后一段代码了,future2 result:625
>从打印结果可知,whenComplete使用原始的执行的任务的线程,所以可以看成是同步执行的,并且新的CompletableFuture对象的结果和原始的一致
whenCompleteAsync:
//如果使用这段代码,则会是和当前线程同步执行
public static void main(String[] args) throws Exception{
CompletableFuture future = CompletableFuture.supplyAsync(() -> getMoreData());
future.whenCompleteAsync((result, exception) -> {
System.out.println("计算已执行完毕,result:" + result);
System.out.println("计算已执行完毕,exception:" + (excetion == null ? "无异常" : excetion.getClass()));
});
System.out.println("执行到最后一段代码了,result:" + future.get());
}
> 打印执行结果:
begin to start compute
end to compute, passed:1551180611064
执行到最后一段代码了,result:323
执行到whenComplete了,result:323
执行到whenComplete了,exception:无异常
>从打印结果可知,whenCompleteAsync是异步执行的
exceptionally比较复杂,需要通过4个实例才能真正明白:
exceptionally实例1:
//这段代码,由于会抛出异常,会先走whenCompleteAsync,然后再走exceptionally,而且是无法获取到返回值的。
public static void main(String[] args) throws Exception{
CompletableFuture future = CompletableFuture.supplyAsync(() -> throwException());
future.whenCompleteAsync((result, exception) -> {
System.out.println("计算已执行完毕,result:" + result);
System.out.println("计算已执行完毕,exception:" + (exception == null ? "无异常" : exception.getClass()));
}).exceptionally(exception -> {
System.out.println("计算执行过程中发生了异常,exception:" + exception.getClass());
return 0;
});
System.out.println("执行到最后一段代码了,future result:" + future.get());
}
> 打印执行结果:
准备抛出异常
抛了
计算已执行完毕,result:null
计算已执行完毕,result:null
计算已执行完毕,exception:class java.util.concurrent.CompletionException
计算已执行完毕,exception:class java.util.concurrent.CompletionException
计算执行过程中发生了异常,exception:class java.util.concurrent.CompletionException
计算执行过程中发生了异常,exception:class java.util.concurrent.CompletionException
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: 主动抛出异常
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
at me.ele.ecs.eapp.service.impl.Main.main(Main.java:69)
Caused by: java.lang.RuntimeException: 主动抛出异常
at me.ele.ecs.eapp.service.impl.Main.throwException(Main.java:37)
at me.ele.ecs.eapp.service.impl.Main.lambda$main$0(Main.java:44)
exceptionally实例2:
//这里的打印结果是和上面类似的,可是为什么这次要获取新的CompletableFuture对象呢?看下面的exceptionally实例3后,再回来对比吧
public static void main(String[] args) throws Exception{
CompletableFuture future = CompletableFuture.supplyAsync(() -> throwException());
CompletableFuture future2 = future.whenCompleteAsync((result, exception) -> {
System.out.println("计算已执行完毕,result:" + result);
System.out.println("计算已执行完毕,exception:" + (exception == null ? "无异常" : exception.getClass()));
});
CompletableFuture future3 = future2.exceptionally(exception -> {
System.out.println("计算执行过程中发生了异常,exception:" + exception.getClass());
return 0;
});
System.out.println("执行到最后一段代码了,future result:" + future.get());
//因为上面的执行过程中,已经抛出了异常了,那么下面的这两段代码是无法执行到的,
System.out.println("执行到最后一段代码了,future2 result:" + future2.get());
System.out.println("执行到最后一段代码了,future3 result:" + future3.get());
}
> 打印执行结果:
准备抛出异常
抛了
计算已执行完毕,result:null
计算已执行完毕,exception:class java.util.concurrent.CompletionException
计算执行过程中发生了异常,exception:class java.util.concurrent.CompletionException
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: 主动抛出异常
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
at me.ele.ecs.eapp.service.impl.Main.main(Main.java:69)
Caused by: java.lang.RuntimeException: 主动抛出异常
at me.ele.ecs.eapp.service.impl.Main.throwException(Main.java:37)
at me.ele.ecs.eapp.service.impl.Main.lambda$main$0(Main.java:44)
exceptionally实例3:
//
public static void main(String[] args) throws Exception{
CompletableFuture future = CompletableFuture.supplyAsync(() -> throwException());
CompletableFuture future2 = future.whenCompleteAsync((result, exception) -> {
System.out.println("计算已执行完毕,result:" + result);
System.out.println("计算已执行完毕,exception:" + (exception == null ? "无异常" : exception.getClass()));
});
CompletableFuture future3 = future2.exceptionally(exception -> {
System.out.println("计算执行过程中发生了异常,exception:" + exception.getClass());
//这里的返回值实际其是没有用处的。因为如果抛出了异常,future的get方法是执行不到的;而如果没有抛出异常的话,还是会返回原始的CompletableFuture的值的
//所以这个exceptionally就是仅仅用来处理异常的。
return 0;
});
//System.out.println("执行到最后一段代码了,future result:" + future.get());
//System.out.println("执行到最后一段代码了,future2 result:" + future2.get());
//和上面实例2唯一的区别就是注释掉了上面两段代码,但是执行结果却不一样了,而且整个main方法都没有抛出来异常,原因就在于future和future2是异步执行的,所以是在别的线程抛了异常,而main方法是不会抛出来的。而且在获取future3的结果时,可以发现,返回了future3对象自定义的返回值
System.out.println("执行到最后一段代码了,future3 result:" + future3.get());
}
> 打印执行结果:
准备抛出异常
抛了
计算已执行完毕,result:null
计算已执行完毕,exception:class java.util.concurrent.CompletionException
计算执行过程中发生了异常,exception:class java.util.concurrent.CompletionException
执行到最后一段代码了,future3 result:0
exceptionally实例4:
public static void main(String[] args) throws Exception{
CompletableFuture future = CompletableFuture.supplyAsync(Main::getMoreData);
CompletableFuture future2 = future.whenCompleteAsync((result, exception) -> {
System.out.println("计算已执行完毕,result:" + result);
System.out.println("计算已执行完毕,exception:" + (exception == null ? "无异常" : exception.getClass()));
});
CompletableFuture future3 = future2.exceptionally(exception -> {
System.out.println("计算执行过程中发生了异常,exception:" + exception.getClass());
return 0;
});
System.out.println("执行到最后一段代码了,future result:" + future.get());
System.out.println("执行到最后一段代码了,future2 result:" + future2.get());
//原始的计算逻辑不变,exceptionally返回的新的CompletableFuture对象的结果和原始计算逻辑返回的结果一致。
System.out.println("执行到最后一段代码了,future3 result:" + future3.get());
}
> 打印执行结果:
begin to start compute
end to compute, passed:1551239497158
getMoreData: 679
执行到最后一段代码了,future result:679
计算已执行完毕,result:679
计算已执行完毕,exception:无异常
执行到最后一段代码了,future2 result:679
执行到最后一段代码了,future3 result:679
2.handle* 方法
和 whenComplete* 方法一样,都是在任务执行完后,执行该方法的逻辑,但是和 whenComplete* 不同的是:
该操作执行完毕后,它返回的新CompletableFuture对象的计算结果是handle*方法的返回值,并不是原始计算逻辑的返回值
//同步
public CompletableFuture handle(BiFunction super T,Throwable,? extends U> fn)
//异步,使用原始CompletableFuture的线程
public CompletableFuture handleAsync(BiFunction super T,Throwable,? extends U> fn)
//异步,使用自定义线程池的线程
public CompletableFuture handleAsync(BiFunction super T,Throwable,? extends U> fn, Executor executor)
实例:
public static void main(String[] args) throws Exception{
CompletableFuture future = CompletableFuture.supplyAsync(Main::getMoreData);
CompletableFuture future2 = future.handleAsync((result, exception) -> {
System.out.println("计算已执行完毕,result:" + result);
System.out.println("计算已执行完毕,exception:" + (exception == null ? "无异常" : exception.getClass()));
return result + 1;
});
System.out.println("执行到最后一段代码了,future result:" + future.get());
System.out.println("执行到最后一段代码了,future2 result:" + future2.get());
}
> 打印执行结果:
begin to start compute
end to compute, passed:1551243326193
getMoreData: 395
执行到最后一段代码了,future result:395
计算已执行完毕,result:395
计算已执行完毕,exception:无异常
执行到最后一段代码了,future2 result:396
3.thenApply* 方法:连接
thenApply* 可以连接多个CompletableFuture对象,相当于将一个一个的CompletableFuture串联起来了,第一个CompletableFuture对象的结果会传递到下一个对象中,并且下一个CompletableFuture对象的结算结果会作为上一个对象的CompletableFuture结果,依次类推,也就是说会改变原始CompletableFuture对象的结果。
注:它和 handle 方法有点类似,都会拿到上一个CompletableFuture对象的结果进行计算,但是区别就是thenApply 会改变原始对象的计算结果,而 handle* 并不会**。
public CompletableFuture thenApply(Function super T,? extends U> fn)
public CompletableFuture thenApplyAsync(Function super T,? extends U> fn)
public CompletableFuture thenApplyAsync(Function super T,? extends U> fn, Executor executor)
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
return 100;
});
//由于这里同时连接了多个thenApplyAsync,第一个是异步的,第二个是同步的,并且都没有处理异常,所以异常会直接在执行计算的线程上抛出来。
CompletableFuture f = future.thenApplyAsync(i -> i * 10).thenApply(i -> i.toString());
System.out.println(f.get()); //"1000"
}
4. thenAccept* 方法:纯消费一个CompletableFuture对象的结果
thenAccept* 返回的新的CompletableFuture对象不返回结果,如果使用get方法,会返回一个null。
public CompletableFuture thenAccept(Consumer super T> action)
public CompletableFuture thenAcceptAsync(Consumer super T> action)
public CompletableFuture thenAcceptAsync(Consumer super T> action, Executor executor)
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture future = CompletableFuture.supplyAsync(Main::getMoreData);
CompletableFuture future2 = future.thenAccept(result -> {
System.out.println("执行到thenAccept了, result:" + result);
});
System.out.println("执行到最后一段代码了,future result:" + future.get());
System.out.println("执行到最后一段代码了,future2 result: " + future2.get());
}
> 打印执行结果:
begin to start compute
end to compute, passed:1551341977684
getMoreData: 171
执行到thenAccept了, result:171
执行到最后一段代码了,future result:171
执行到最后一段代码了,future2 result: null
5. thenAcceptBoth* 方法:在两个CompletableFuture对象的执行完后执行。
它和 thenAccept 一样,都是纯消费,但是thenAccept*只能消费一个CompletableFuture对象,而thenAcceptBoth* 能在两个不同的CompletableFuture对象执行完成后,消费他们两个的计算结果。
而且他仅仅在原始的两个CompletableFuture对象都计算成功之后,才开始执行。
public CompletableFuture thenAcceptBoth(CompletionStage extends U> other, BiConsumer super T,? super U> action)
public CompletableFuture thenAcceptBothAsync(CompletionStage extends U> other, BiConsumer super T,? super U> action)
public CompletableFuture thenAcceptBothAsync(CompletionStage extends U> other, BiConsumer super T,? super U> action, Executor executor)
//runAfterBoth和上面三个的区别就是它不消费原始的CompletableFuture结果
public CompletableFuture runAfterBoth(CompletionStage> other, Runnable action)
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture future = CompletableFuture.supplyAsync(Main::getMoreData);
CompletableFuture future2 = CompletableFuture.supplyAsync(Main::getMoreData);
future.thenAcceptBothAsync(future2, (x, y) -> {
System.out.println("future1 和 future都执行完成了,结果分别是:" + x + "," + y);
});
System.out.println("执行到最后一段代码了,future result:" + future.get());
System.out.println("执行到最后一段代码了,future2 result: " + future2.get());
}
> 打印执行结果:
begin to start compute
begin to start compute
end to compute, passed:1551342475808
getMoreData: 920
执行到最后一段代码了,future result:920
end to compute, passed:1551342475811
getMoreData: 747
执行到最后一段代码了,future2 result: 747
future1 和 future都执行完成了,结果分别是:920,747
6. thenRun* 方法:不消费CompletableFuture对象的结果,执行一个新任务。
在原始CompletableFuture执行任务结束后,而且执行指定的任务,不消费,也不产生结果。
public CompletableFuture thenRun(Runnable action)
public CompletableFuture thenRunAsync(Runnable action)
public CompletableFuture thenRunAsync(Runnable action, Executor executor)
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture future = CompletableFuture.supplyAsync(Main::getMoreData);
CompletableFuture future2 = future.thenRunAsync(() -> {
System.out.println("future执行完成了");
});
System.out.println("执行到最后一段代码了,future result:" + future.get());
System.out.println("执行到最后一段代码了,future2 result:" + future2.get());
}
> 打印执行结果:
begin to start compute
end to compute, passed:1551344347162
getMoreData: 688
执行到最后一段代码了,future result:688
future执行完成了
执行到最后一段代码了,future2 result:null
7.acceptEither* :当任意一个CompletableFuture计算完成的时候就会执行,它没有返回值。
runAfterBoth是当两个CompletableFuture都计算完成后才执行。
public CompletableFuture acceptEither(CompletionStage extends T> other, Consumer super T> action)
public CompletableFuture acceptEitherAsync(CompletionStage extends T> other, Consumer super T> action)
public CompletableFuture acceptEitherAsync(CompletionStage extends T> other, Consumer super T> action, Executor executor)
8.applyToEither*:当任意一个CompletableFuture计算完成的时候就会执行,它有返回值。
runAfterBoth是当两个CompletableFuture都计算完成后才执行。而 acceptEither* 没有返回值。
public CompletableFuture applyToEither(CompletionStage extends T> other, Function super T,U> fn)
public CompletableFuture applyToEitherAsync(CompletionStage extends T> other, Function super T,U> fn)
public CompletableFuture applyToEitherAsync(CompletionStage extends T> other, Function super T,U> fn, Executor executor)
辅助方法 :allOf 和 anyOf
public static CompletableFuture allOf(CompletableFuture>... cfs)
public static CompletableFuture
更进一步
如果你用过Guava的Future类,你就会知道它的Futures辅助类提供了很多便利方法,用来处理多个Future,而不像Java的CompletableFuture,只提供了allOf、anyOf两个方法。 比如有这样一个需求,将多个CompletableFuture组合成一个CompletableFuture,这个组合后的CompletableFuture的计算结果是个List,它包含前面所有的CompletableFuture的计算结果,guava的Futures.allAsList可以实现这样的功能,但是对于java CompletableFuture,我们需要一些辅助方法:
public static CompletableFuture> sequence(List> futures) {
CompletableFuture allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}
public static CompletableFuture> sequence(Stream> futures) {
List> futureList = futures.filter(f -> f != null).collect(Collectors.toList());
return sequence(futureList);
}
Java Future转CompletableFuture:
public static CompletableFuture toCompletable(Future future, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}, executor);
}
本文大量参考了鸟窝的文章,本人只是将实例便于理解。
地址:https://colobu.com/2016/02/29/Java-CompletableFuture/