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  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 action)
//有Async,异步处理正常计算结果或异常,使用执行任务的那个线程池中的线程来执行该方法!所以这个方法是异步的。
public CompletableFuture whenCompleteAsync(BiConsumer action)
//有Async,异步处理正常计算结果或异常,使用自定义线程池来执行该方法,所以这个方法是异步的。
public CompletableFuture whenCompleteAsync(BiConsumer 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 fn)
//异步,使用原始CompletableFuture的线程
public  CompletableFuture 	handleAsync(BiFunction fn)
//异步,使用自定义线程池的线程
public  CompletableFuture 	handleAsync(BiFunction 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 fn)
public  CompletableFuture 	thenApplyAsync(Function fn)
public  CompletableFuture 	thenApplyAsync(Function 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 action)
public CompletableFuture 	thenAcceptAsync(Consumer action)
public CompletableFuture 	thenAcceptAsync(Consumer 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 other, BiConsumer action)
public  CompletableFuture 	thenAcceptBothAsync(CompletionStage other, BiConsumer action)
public  CompletableFuture 	thenAcceptBothAsync(CompletionStage other, BiConsumer 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 other, Consumer action)
public CompletableFuture 	acceptEitherAsync(CompletionStage other, Consumer action)
public CompletableFuture 	acceptEitherAsync(CompletionStage other, Consumer action, Executor executor)

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

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

public  CompletableFuture 	applyToEither(CompletionStage other, Function fn)
public  CompletableFuture 	applyToEitherAsync(CompletionStage other, Function fn)
public  CompletableFuture 	applyToEitherAsync(CompletionStage other, Function fn, Executor executor)

辅助方法 :allOf 和 anyOf

public static CompletableFuture 	    allOf(CompletableFuture... cfs)
public static CompletableFuture 	anyOf(CompletableFuture... cfs)
 
   

更进一步

如果你用过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/

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

你可能感兴趣的:(java8 CompletableFuture入门 使用教程 详解所有方法 附实例)