CompletableFuture 使用详解

3,290 阅读6分钟

1、 runAsync 和 supplyAsync方法

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)

没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。runAsync 和 supplyAsync 区别:

  • runAsync方法不支持返回值。
  • supplyAsync可以支持返回值。

示例

//无返回值
public static void testRunAsync() throws Exception {
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println("run end ...");
    });
    
    future.get();
}

//有返回值
public static void testSupplyAsync() throws Exception {         
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println("run end ...");
        return System.currentTimeMillis();
    });

    long time = future.get();
    System.out.println("time = "+time);
}

2、计算结果完成时的回调方法

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

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)

可以看到Action的类型是BiConsumer<? super T,? super Throwable>它可以处理正常的计算结果,或者异常情况。

whenComplete 和 whenCompleteAsync 有什么区别呢?

  • Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFutureor by any other caller of a completion method. 传递给 non-async 方法作为参数的函数(action)可能会在完成当前的 CompletableFuture 的线程中执行,也可能会在完成方法的调用者线程中执行。
  • All async methods without an explicit Executor argument are performed using the ForkJoinPool.commonPool() (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run each task). To simplify monitoring, debugging, and tracking, all generated asynchronous tasks are instances of the marker interface CompletableFuture.AsynchronousCompletionTask. 所有没有Executor 参数的 async 方法都在 ForkJoinPool.commonPool() 线程池中执行(除非不支持最小并发度为2,这种情况下,每个任务会创建一个新线程去执行)。为了简化监控、调试和追踪,所有生成的异步任务都是接口 CompletableFuture.AsynchronousCompletionTask 的实例。

上面这段话引用自 CompletableFuture api 文档。这段说明描述了 async 和 non-async 方法的区别,这个说明适用于本文后面提到的所有 async 和 non-async 方法。

对于 non-async 方法,需要特别说明一下。在依赖任务完成之前注册的函数(通过whenComplete、thenApply 等方法),也就是在CompletableFuture 的complete() 被调用之前被注册的函数,将会在完成依赖任务的线程中执行。如果注册的时候,依赖任务已经完成,那么注册的函数将会在调用注册的线程中执行。

示例

public static void testWhenComplete() throws Exception {
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        if(new Random().nextInt()%2>=0) {
            int i = 12/2;
        }
        System.out.println(Thread.currentThread().getName()+", run end ...");
    });

    future.whenComplete(new BiConsumer<Void, Throwable>() {
        @Override
        public void accept(Void t, Throwable action) {
            System.out.println(Thread.currentThread().getName()+", whenComplete 1 执行完成!");
        }
    });
    future.whenComplete(new BiConsumer<Void, Throwable>() {
        @Override
        public void accept(Void t, Throwable action) {
            System.out.println(Thread.currentThread().getName()+", whenComplete 2 执行完成!");
        }
    });
    future.exceptionally(new Function<Throwable, Void>() {
        @Override
        public Void apply(Throwable t) {
            System.out.println(Thread.currentThread().getName()+", 执行失败!"+t.getMessage());
            return null;
        }
    });

    TimeUnit.SECONDS.sleep(2);
}
-------------------whenComplete 打印输出
ForkJoinPool.commonPool-worker-1, run end ...
ForkJoinPool.commonPool-worker-1, whenComplete 2 执行完成!
ForkJoinPool.commonPool-worker-1, whenComplete 1 执行完成!
-------------------将测试方法中的whenComplete 换成 whenCompleteAsync 打印输出
ForkJoinPool.commonPool-worker-1, run end ...
ForkJoinPool.commonPool-worker-1, whenCompleteAsync 2 执行完成!
ForkJoinPool.commonPool-worker-2, whenCompleteAsync 1 执行完成!

可以看到,对于whenComplete,两次whenComplete 调用都和上一个任务在同一个线程中执行;而对于whenCompleteAsync,两次whenCompleteAsync 在不同的线程中执行。两种方式都时在ForkJoinPool 线程池中执行,而不是调用线程中执行。

3、 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)

Function<? super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型

示例

private static void testThenApply() throws Exception {
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {
        @Override
        public Long get() {
            long result = new Random().nextInt(100);
            System.out.println(Thread.currentThread().getName()+", result1="+result);
            return result;
        }
    }).thenApplyAsync(new Function<Long, Long>() {
        @Override
        public Long apply(Long t) {
            long result = t*5;
            System.out.println(Thread.currentThread().getName()+", result2="+result);
            return result;
        }
    });

    long result = future.get();
    System.out.println(Thread.currentThread().getName()+", result=" + result);
}

第二个任务依赖第一个任务的结果。

-------------------thenApply 打印输出
ForkJoinPool.commonPool-worker-1, result1=95
main, result2=475
main, result=475
-------------------thenApplyAsync 打印输出
ForkJoinPool.commonPool-worker-1, result1=13
ForkJoinPool.commonPool-worker-1, result2=65
main, result=65

4、 handle 方法

handle 是执行任务完成时对结果的处理。
handle 方法和 thenApply 方法处理方式基本一样,也是在调用方线程中执行。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

示例

void testHandle() throws Exception{
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {

        @Override
        public Integer get() {
            System.out.println(Thread.currentThread().getName() + ", in async task" );
            int i= 10/0;
            return new Random().nextInt(10);
        }
    }).handle(new BiFunction<Integer, Throwable, Integer>() {
        @Override
        public Integer apply(Integer param, Throwable throwable) {
            int result = -1;
            if(throwable==null){
                result = param * 2;
            }else{
                System.out.println(Thread.currentThread().getName() + "," + throwable.getMessage());
            }
            return result;
        }
    });
    System.out.println(Thread.currentThread().getName() + "," + future.get());
}
测试程序打印输出:
ForkJoinPool.commonPool-worker-1, in async task
main,java.lang.ArithmeticException: / by zero
main,-1

从示例中可以看出,在 handle 中可以根据任务是否有异常来进行做相应的后续处理操作。而 thenApply 方法,如果上个任务出现错误,则不会执行 thenApply 方法。

5、 thenAccept 消费处理结果

接收任务的处理结果,并消费处理,无返回结果。

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

示例

public void testThenAccept() throws Exception{
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            System.out.println(Thread.currentThread().getName() + ", in async task" );
            return new Random().nextInt(10);
        }
    }).thenAccept(integer -> {
        System.out.println(Thread.currentThread().getName() + ", integer=" + integer);
    });
    future.get();
    System.out.println(Thread.currentThread().getName() + ", caller" );
}
测试程序打印输出:
ForkJoinPool.commonPool-worker-1, in async task
main, integer=1
main, caller

从示例代码中可以看出,该方法只是消费执行完成的任务,并可以根据上面的任务返回的结果进行处理。

6、thenRun 方法

跟 thenAccept 方法不一样的是,不关心任务的处理结果。只要上面的任务执行完成,就开始执行 thenAccept 。

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

示例

void testThenRun() throws Exception{
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            System.out.println(Thread.currentThread().getName() + ", in async task" );
            return new Random().nextInt(10);
        }
    }).thenRun(() -> {
        System.out.println(Thread.currentThread().getName() + ", in thenRun" );
    });
    future.get();
}
-------------------thenApplyAsync 打印输出
ForkJoinPool.commonPool-worker-1, in async task
main, in thenRun
-------------------thenRunAsync 打印输出
ForkJoinPool.commonPool-worker-1, in async task
ForkJoinPool.commonPool-worker-1, in thenRun

7、thenCombine 合并任务

thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

示例

void thenCombine() throws Exception {
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println(Thread.currentThread().getName() + ", in async task 1" );
            return "hello";
        }
    });
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println(Thread.currentThread().getName() + ", in async task 2" );
            return "hello";
        }
    });
    CompletableFuture<String> result = future1.thenCombine(future2, new BiFunction<String, String, String>() {
        @Override
        public String apply(String t, String u) {
            System.out.println(Thread.currentThread().getName() + ", in combine task" );
            return t+" "+u;
        }
    });
    System.out.println(Thread.currentThread().getName() + "," + result.get());
}
-------------------thenCombine 打印输出
ForkJoinPool.commonPool-worker-1, in async task 1
ForkJoinPool.commonPool-worker-1, in async task 2
main, in combine task
main,hello hello
-------------------thenCombineAsync 打印输出
ForkJoinPool.commonPool-worker-1, in async task 1
ForkJoinPool.commonPool-worker-1, in async task 2
ForkJoinPool.commonPool-worker-1, in combine task
main,hello hello

8、thenCombine / thenAcceptBoth / runAfterBoth

thenCombine / thenAcceptBoth / runAfterBoth都表示:将两个CompletableFuture组合起来,只有这两个都正常执行完了,才会执行某个任务

区别在于:

  • thenCombine:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
  • thenAcceptBoth: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
  • runAfterBoth 不会把执行结果当做方法入参,且没有返回值。

9、applyToEither / acceptEither / runAfterEither

applyToEither / acceptEither / runAfterEither 都表示:将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。

区别在于:

  • applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
  • acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
  • runAfterEither: 不会把执行结果当做方法入参,且没有返回值。