java8 CompletableFuture入门 使用教程 详解所有方法 附实例

概览

  1. CompletableFuture是java8引入的新类,该类实现了 Future 接口和 CompletionStage 接口,封装了future、forkjoin相关类来执行异步,所以你还是可以像以前一样通过阻塞(get)或者轮询的方式获得结果,尽管这种方式不推荐使用。
  2. CompletionStage 接口代表异步计算中的 不同阶段,以及如何 组合 这些计算阶段。
  3. CompletableStage 接口中有 50 多个方法,可以对 CompletableStage 进行组合、计算,方法看似很多,但可以按功能对其分类,大多数方法都有 3 种变体:
    1. 不带 Async 方法:同步方法
    2. 带 Async,只有一个参数:异步方法,使用默认的 ForkJoinPool.commonPool() 获取线程池
    3. 带 Async,有两个参数:异步方法,且使用第二个参数指定的 ExecutorService 线程池

了解CompletableFuture需要理解java8的函数式接口,不了解的同学可以先移步:https://my.oschina.net/u/3244997/blog/3014977

创建CompletableFuture对象

//比较特殊,他入参就是返回值,也就是说他可以用来执行需要其他返回值的异步任务。
public static <U> CompletableFuture<U> completedFuture(U value)

//无返回值,使用默认线程池
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)

//
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

举例:

//supplyAsync方法无入参,但是返回一个String对象。此方法使用了默认的线程池执行异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    //长时间的计算任务
    return "·00";
});

在创建完CompletableFuture对象并且执行任务之后,还可以对结果或者异常等进行额外的操作或任务

下面将通过具体例子来展示各个方法的使用。

1.whenComplete* 和 exceptionally 方法

当原始的CompletableFuture任务执行完后,不管是否成功计算出结果,还是抛出异常,都会会执行 whenComplete* 或 exceptionally 的方法中的任务
该操作执行完毕后:

  • 会返回一个新的CompletableFuture对象!!!
  • 使用whenComplete*方法时,返回的新的CompletableFuture对象的返回结果和原始的CompletableFuture对象计算结果相同
  • 使用 exceptionally方法时,如果原始计算逻辑抛出异常,那么返回的 新的CompletableFuture对象 的返回结果由该方法的return值决定;如果原始计算逻辑没有抛出异常,那么返回的 新的CompletableFuture对象 的返回结果和原始计算逻辑返回的结果一致。有点绕,先不明白没关系,下面会有四个exceptionally实例解释这段话。
BiConsumer<T,U> 函数接口有两个参数,无返回值。
Function<T,R> 函数接口有一个输入参数,返回一个结果。

//无Async,同步处理正常计算结果或异常,使用执行任务的那个线程来执行该方法,所以这个方法是同步的。
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
//有Async,异步处理正常计算结果或异常,使用执行任务的那个线程池中的线程来执行该方法!所以这个方法是异步的。
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
//有Async,异步处理正常计算结果或异常,使用自定义线程池来执行该方法,所以这个方法是异步的。
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? superThrowable> action, Executor executor)
//处理异常。
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> 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<Integer> future = CompletableFuture.supplyAsync(() -> getMoreData());

        CompletableFuture<Integer> 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<Integer> 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<Integer> 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<Integer> future = CompletableFuture.supplyAsync(() -> throwException());

        CompletableFuture<Integer> future2 = future.whenCompleteAsync((result, exception) -> {
            System.out.println("计算已执行完毕,result:" + result);
            System.out.println("计算已执行完毕,exception:" + (exception == null ? "无异常" : exception.getClass()));
        });

        CompletableFuture<Integer> 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<Integer> future = CompletableFuture.supplyAsync(() -> throwException());

        CompletableFuture<Integer> future2 = future.whenCompleteAsync((result, exception) -> {
            System.out.println("计算已执行完毕,result:" + result);
            System.out.println("计算已执行完毕,exception:" + (exception == null ? "无异常" : exception.getClass()));
        });

        CompletableFuture<Integer> 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<Integer> future = CompletableFuture.supplyAsync(Main::getMoreData);

        CompletableFuture<Integer> future2 = future.whenCompleteAsync((result, exception) -> {
            System.out.println("计算已执行完毕,result:" + result);
            System.out.println("计算已执行完毕,exception:" + (exception == null ? "无异常" : exception.getClass()));
        });

        CompletableFuture<Integer> 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 <U> CompletableFuture<U> 	handle(BiFunction<? super T,Throwable,? extends U> fn)
//异步,使用原始CompletableFuture的线程
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)
实例:

    public static void main(String[] args) throws Exception{

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(Main::getMoreData);

        CompletableFuture<Integer> 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 <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)
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        return 100;
        });
       //由于这里同时连接了多个thenApplyAsync,第一个是异步的,第二个是同步的,并且都没有处理异常,所以异常会直接在执行计算的线程上抛出来。
       CompletableFuture<String> 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<Void> 	thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> 	thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> 	thenAcceptAsync(Consumer<? super T> action, Executor executor)
public static void main(String[] args) throws ExecutionException, InterruptedException {
         CompletableFuture<Integer> future = CompletableFuture.supplyAsync(Main::getMoreData);

        CompletableFuture<Void> 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 <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)
public <U> CompletableFuture<Void> 	thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor)

//runAfterBoth和上面三个的区别就是它不消费原始的CompletableFuture结果
public     CompletableFuture<Void> 	runAfterBoth(CompletionStage<?> other,  Runnable action)
public static void main(String[] args) throws ExecutionException, InterruptedException {
         CompletableFuture<Integer> future = CompletableFuture.supplyAsync(Main::getMoreData);

        CompletableFuture<Integer> 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<Void> 	thenRun(Runnable action)
public CompletableFuture<Void> 	thenRunAsync(Runnable action)
public CompletableFuture<Void> 	thenRunAsync(Runnable action, Executor executor)
public static void main(String[] args) throws ExecutionException, InterruptedException {
         CompletableFuture<Integer> future = CompletableFuture.supplyAsync(Main::getMoreData);

        CompletableFuture<Void> 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<Void> 	acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> 	acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> 	acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)

8.applyToEither*:当任意一个CompletableFuture计算完成的时候就会执行,它有返回值。

runAfterBoth是当两个CompletableFuture都计算完成后才执行。而 acceptEither* 没有返回值。

public <U> CompletableFuture<U> 	applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> 	applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> 	applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)

辅助方法 :allOf 和 anyOf

public static CompletableFuture<Void> 	    allOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Object> 	anyOf(CompletableFuture<?>... cfs)

更进一步

如果你用过Guava的Future类,你就会知道它的Futures辅助类提供了很多便利方法,用来处理多个Future,而不像Java的CompletableFuture,只提供了allOf、anyOf两个方法。 比如有这样一个需求,将多个CompletableFuture组合成一个CompletableFuture,这个组合后的CompletableFuture的计算结果是个List,它包含前面所有的CompletableFuture的计算结果,guava的Futures.allAsList可以实现这样的功能,但是对于java CompletableFuture,我们需要一些辅助方法:

   public static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
       CompletableFuture<Void> allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
       return allDoneFuture.thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.<T>toList()));
   }
public static <T> CompletableFuture<Stream<T>> sequence(Stream<CompletableFuture<T>> futures) {
       List<CompletableFuture<T>> futureList = futures.filter(f -> f != null).collect(Collectors.toList());
       return sequence(futureList);
   }

Java Future转CompletableFuture:

public static <T> CompletableFuture<T> toCompletable(Future<T> 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/

转载于:https://my.oschina.net/u/3244997/blog/2248986

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值