新しいJava8同時実行機能CompletableFutureを知っていますか?

創造を続け、成長を加速させましょう!「ナゲッツデイリーニュープラン・6月アップデートチャレンジ」に参加した初日です。クリックしてイベントの詳細をご覧ください。

1. CompletableFutureとは何ですか?

一日中ビジネス開発に没頭していると、技術的な更新に注意を払わず、実用的でシンプルな方法を無視していると感じますか?以前は、非同期タスクを実行するときに、 CallableまたはRunnableインターフェースを実装するために、今日はそれとは異なるCompletableFutureクラスについて説明します。

CompletableFutureは、Futureインターフェースを改善しました。Callable/ Runnableインターフェースと比較して、マルチタスクチェーン呼び出し、組み合わせ、およびマルチタスク並行処理をサポートします。設計プロセスでは、非同期タスクの結果を直接取得し、それを次のタスクに渡して次のプロセスを続行したいことがよくあります。このとき、CompletableFutureの役割があります。

  • CompletableFutureクラス図

次のクラス図からわかるように、CompletableFutureはFutureおよびCompletionStageインターフェースを実装し、Futureはタスク実行結果とタスク実行ステータスを取得する機能を提供します。CompletionStageは、タスクの実行段階を表し、複数のタスクの集計関数をサポートするための多くのメソッドを提供します。

CompletableFuture.jpg

2.CompletableFutureのメソッドを使用するための手順

2.1 CompletableFutureクラスは、非同期操作のためのいくつかの静的メソッドを提供します。

supplyAsyncとrunAsyncは、主に非同期イベントを作成するために使用されます。

  • supplyAsync带有返回值的异步任务,支持在默认线程池ForkJoinPool.commonPool()中完成异步任务,也可以使用自定义线程池执行异步任务,结果返回一个新的CompletableFuture,返回结果类型U。最终的任务执行结果可通过返回CompletableFuture对象的 get()/join() 方法获取返回值。
// 使用默认线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {...}
// 使用自定义线程池Executor
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) {...}

// ====================================demo华丽分割线============================================
CompletableFuture<String> supplyAsyncFuture = CompletableFuture.supplyAsync(() -> {
    log.info("executing supplyAsync task ...");
    return "this is supplyAsync";
});
// 进入阻塞获取异步任务结果
log.info(supplyAsyncFuture.get());  // 输出结果:this is supplyAsync
复制代码
  • runAsync不带返回值的异步任务,支持在默认线程池ForkJoinPool.commonPool()中完成异步任务,也可以使用自定义线程池执行异步任务,结果返回一个新的CompletableFuture,返回结果类型为Void,也就是无返回值。
public static CompletableFuture<Void> runAsync(Runnable runnable) {...}
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {...}

// ====================================demo华丽分割线============================================
CompletableFuture<Void> runAsyncFuture = CompletableFuture.runAsync(() -> {
    log.info("executing runAsync task ...");
});
runAsyncFuture.get();
复制代码
  • allOf:多个CompletableFuture任务并发执行,所有CompletableFuture任务完成时,返回一个新的CompletableFuture对象,其返回值为Void,也就是无返回值。
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {...}

// ====================================demo华丽分割线============================================
// allOf,可传递返回值不同类型的future,最终结果按自己设计预期处理即可
CompletableFuture<String> cf11 = CompletableFuture.supplyAsync(() -> {
    log.info("executing supplyAsync task cf11 ...");
    return "this is supplyAsync";
});
CompletableFuture<String> cf12 = CompletableFuture.supplyAsync(() -> {
    log.info("executing supplyAsync task cf12 ...");
    return "this is supplyAsync";
});
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(cf11, cf12);
allOfFuture.get();
复制代码
  • anyOf:多个CompletableFuture任务并发执行,只要有一个CompletableFuture任务完成时,就会返回一个新的CompletableFuture对象,并返回该CompletableFuture执行完成任务的返回值。
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {...}

// ====================================demo华丽分割线============================================
CompletableFuture<String> cf21 = CompletableFuture.supplyAsync(() -> {
    log.info("executing supplyAsync task cf21 ...");
    return "this is supplyAsync cf21";
});
CompletableFuture<String> cf22 = CompletableFuture.supplyAsync(() -> {
    log.info("executing supplyAsync task cf22 ...");
    return "this is supplyAsync cf22";
});
CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(cf21, cf22);
log.info("{}", anyOfFuture.get());  // 输出结果:this is supplyAsync cf21或cf22
复制代码

2.2 获取异步任务执行结果的方法 get()/join()

join()和get()方法都是CompletableFuture对象基于阻塞的方式来获取异步任务执行结果。

  • get方法会抛出显示异常必须捕获处理,任务允许被中断抛出InterruptedException异常,通过带有超时时间的阻塞方式获取异步任务执行结果,超时等待无结果则中断任务抛出TimeoutException异常。
  • join方法会抛出未检查异常,与get()方法不同的是join()方法不允许被中断。
// 可中断,可设置超时时间
public T get() throws InterruptedException, ExecutionException {...}
public T get(long timeout, TimeUnit unit) throws InterruptedException, 
                ExecutionException, TimeoutException {...}
/**
* 不可中断
*/
public T join() {...}
复制代码

3.CompletionStage的方法使用说明

CompletionStage表示一个任务的执行阶段,每个任务都会返回一个CompletionStage对象,可以对多个CompletionStage对象进行串行、并行或者聚合的方式来进行下阶段的操作,也就是说实现异步任务的回调功能。CompletionStage总共提供了38个方法来实现多个CompletionStage任务的各种操作, 接下来我们就针对这些方法分类来了解一下。

以下类型均有三种使用方式:

  • thenAccept:方法名不带Async的使用主线程同步执行回调函数,不做异步处理
  • thenAcceptAsync:方法名带Async,但是无executor参数的,使用默认线程池ForkJoinPool.commonPool异步执行任务
  • thenAcceptAsync:方法名带Async,有executor参数的,使用自定义线程池异步执行任务

3.1 纯消费类型

  • 依赖单个任务完成(thenAccept):由上一个CompletionStage任务执行完成的结果传递到action进行回调处理,即仅仅消费了上一个CompletionStage任务的返回值,回调处理结果无返回值。
// 不使用线程池,仅依赖当前线程执行,不做异步
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
// 使用默认线程池ForkJoinPool.commonPool执行任务
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
// 使用自定义线程池执行任务
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor);

// ====================================demo华丽分割线============================================
CompletableFuture.supplyAsync(() -> "this is supplyAsync")
        .thenAcceptAsync((result) -> {
            log.info("{} thenAcceptAsync", result);
        }).join();
        
// 输出结果:this is supplyAsync thenAcceptAsync
复制代码
  • 依赖两个任务都完成(thenAcceptBoth):两个CompletionStage任务并发执行,必须都完成了才执行action回调处理,即仅仅消费了两个CompletionStage任务的返回值,回调处理结果无返回值。
/**
* 额外多了CompletionStage参数表示CompletionStage任务依赖的另一个CompletionStage任务
* action接收两个参数,分别表示两个CompletionStage任务的返回值
*/
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other, 
                            BiConsumer<? super T, ? super U> action);
// 原理同上,使用默认线程池执行异步任务
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, 
                            BiConsumer<? super T, ? super U> action);
// 原理同上,使用自定义线程池执行异步任务
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, 
                            BiConsumer<? super T, ? super U> action, Executor executor);

// ====================================demo华丽分割线============================================
CompletableFuture<String> cf311 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf311");
CompletableFuture<String> cf312 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf312");
cf311.thenAcceptBothAsync(cf312, (r1, r2) -> {
   log.info("{} and {}", r1, r2);
}).join();
// 输出结果:this is supplyAsync cf311 and this is supplyAsync cf312
复制代码
  • 依赖两个任务中的任何一个完成(acceptEither):两个CompletionStage任务并发执行,只要其中一个先完成了就携带返回值执行action回调处理,即仅仅消费了优先完成的CompletionStage任务的返回值,回调处理结果无返回值。
/**
* 类似thenAcceptBothAsync,只不过acceptEither只需两个任务中的其中一个完成即可回调action
* action中的值为两个任务中先执行完任务的返回值
*/
public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,
                             Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
                             Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
                             Consumer<? super T> action, Executor executor);
                             
// ====================================demo华丽分割线============================================
CompletableFuture<String> cf311 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf311");
CompletableFuture<String> cf312 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf312");
cf311.acceptEitherAsync(cf312, (r) -> {
    log.info(r); // 输出结果:this is supplyAsync cf311或cf312
}).join();
复制代码

3.2 有返回值类型

  • 依赖单个任务完成(thenApply):由上一个CompletionStage任务执行完成的结果传递到action进行回调处理,即不止消费了上一个CompletaionStage任务的返回值,同时回调处理结果也有返回值
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor);

// ====================================demo华丽分割线============================================
CompletableFuture<String> cf32 = CompletableFuture.supplyAsync(() -> "this is supplyAsync")
        .thenApplyAsync(result -> result + " and thenApplyAsync");
log.info(cf32.join());  // 输出结果:this is supplyAsync and thenApplyAsync
复制代码
  • 依赖两个任务都完成(thenCombine):两个CompletionStage任务并发执行,必须都完成了才执行action回调处理,即不止消费了两个CompletaionStage任务的返回值,同时回调处理结果也有返回值。
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);
                             
// ====================================demo华丽分割线============================================
CompletableFuture<String> cf321 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf321");
CompletableFuture<String> cf322 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf322");
CompletableFuture<String> thenCombineFuture = cf321.thenCombineAsync(cf322, (r1, r2) -> {
    return r1 + " and " + r2;
});
log.info(thenCombineFuture.join());
// 输出结果:this is supplyAsync cf321 and this is supplyAsync cf322
复制代码
  • 依赖两个任务中的任何一个完成(applyToEither):两个CompletionStage任务并发执行,只要其中一个任务执行完成就会action回调处理,即不止消费了优先完成的CompletionStage的返回值,同时回调处理结果也有返回值。
// 原理同3.1的acceptEither,只不过applyToEither任务执行完成会返回一个带有返回值的CompletionStage
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,
                             Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,
                             Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,
                             Function<? super T, U> fn, Executor executor);
                             
// ====================================demo华丽分割线============================================

CompletableFuture<String> cf321 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf321");
CompletableFuture<String> cf322 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf322");
CompletableFuture<String> thenCombineFuture = cf321.applyToEitherAsync(cf322, (r) -> {
    return r;
});
log.info(thenCombineFuture.join());
// 输出结果:this is supplyAsync cf321或cf322
复制代码

3.3 不消费也不返回类型

  • 依赖单个任务完成(thenRun):单个CompletionStage任务执行完成回调action处理,即执行action回调方法无参数,回调处理结果也无返回值。
// 上一个CompletionStage任务执行完成后直接回调action处理,无返回值
public CompletionStage<Void> thenRun(Runnable action);
// 同上,使用默认线程池执行action处理
public CompletionStage<Void> thenRunAsync(Runnable action);
// 同上,使用自定义线程池执行action处理
public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor);

// ====================================demo华丽分割线============================================
CompletableFuture.runAsync(() -> {
    // TODO
}).thenRunAsync(() -> {
    log.info("this is thenRunAsync");  // 输出结果:this is thenRunAsync
}).join();
复制代码
  • 依赖两个任务都完成(runAfterBoth):两个CompletionStage任务并发执行,必须两个任务都完成才执行action回调处理,即执行action回调方法无参数,回调处理结果也无返回值。
// 原理同3.1的thenAcceptBoth,只不过runAfterBoth的action回调处理不接收参数且任务执行完成无返回值
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action);
// 同上,使用默认线程池执行action处理
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action);
// 同上,使用自定义线程池执行action处理
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor);

// ====================================demo华丽分割线============================================
CompletableFuture<String> cf331 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf331");
CompletableFuture<String> cf332 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf332");
cf331.runAfterBoth(cf332, () -> {
    log.info("this is runAfterBoth");
}).join();
// 输出结果:this is runAfterBoth
复制代码
  • 依赖两个任务中的任何一个完成(runAfterEither):两个CompletionStage任务并发执行,只需其中任何一个任务完成即可回调action处理,即执行action回调方法无参数,回调处理结果也无返回值。
public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor);

// ====================================demo华丽分割线============================================
CompletableFuture<String> cf331 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf331");
CompletableFuture<String> cf332 = CompletableFuture.supplyAsync(() -> "this is supplyAsync cf332");
cf331.runAfterEitherAsync(cf332, () -> {
    log.info("this is runAfterEitherAsync");
}).join();
// 输出结果:this is runAfterEitherAsync
复制代码

3.4 组合类型

  • thenCompose:存在先后关系的两个任务进行串行组合,由第一个CompletionStage任务执行结果作为参数传递给第二个CompletionStage任务,最终返回第二个CompletionStage。
public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor);

// ====================================demo华丽分割线============================================
CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
    return "this is supplyAsync";
});
CompletableFuture<String> thenComposeFuture = supplyFuture.thenComposeAsync((r) -> {
    return CompletableFuture.supplyAsync(() -> {
        return r + " and this is thenComposeAsync";
    });
});
log.info(thenComposeFuture.join());
// 输出结果:this is supplyAsync and this is thenComposeAsync
复制代码

3.5 任务事件类型

CompletionStage接口也支持类似我们常用的try-catch-finally中的finally的作用,无论这个任务的执行结果是正常还是出现异常的情况,都必须要去执行的一个代码块。在CompletionStage接口提供了以下两种接口回调的形式(whenComplete、handle),并支持主线程同步执行同时也支持使用默认线程池,或者使用自定义线程池去异步执行最终的回调处理。例如我们一个事务操作,无论这段代码执行是否成功,我们都必须要去关闭事务。

  • 任务完成事件(whenComplete):结果无返回值,若出现异常执行完whenComplete回调处理完成后将中断主线程的运行
// 1.whenComplete回调函数中Throwable对象不对空代表出现异常,为空则表示无异常
public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor);

// ====================================demo华丽分割线============================================
CompletableFuture<String> whenCompleteFufute = CompletableFuture.supplyAsync(() -> {
    int a = 0;
    int b = 100 / a;
    return "this is supplyAsync normal";
}).whenCompleteAsync((r, th) -> {
    if (th != null) {
        log.error("this is whenCompleteAsync error");
    }
    else {
        log.info("this is whenCompleteAsync success");
    }
});
log.info(whenCompleteFufute.join());  // 输出结果:this is whenCompleteAsync error
复制代码
  • 任务完成回调事件(handle):结果有返回值,若出现异常执行完handle回调处理完成后将继续执行主线程的后续操作,不中断主线程运行
// 2.handle回调函数中Throwable对象不对空代表出现异常,为空则表示无异常
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);

// ====================================demo华丽分割线============================================
CompletableFuture<String> whenCompleteFufute = CompletableFuture.supplyAsync(() -> {
    int a = 0;
    int b = 100 / a;
    return "this is supplyAsync normal";
}).handleAsync((r, th) -> {
    if (th != null) {
        return "this is handleAsync error";
    }
    else {
        return "this is handleAsync success";
    }
});
log.info(whenCompleteFufute.join());
// 输出结果:this is handleAsync error
log.info("main thread is running");
// 输出结果:main thread is running
复制代码

4.CompletionStage异常处理方法

  • 例外的に:プログラムである限り、CompletionStageタスクなどの例外が発生します。実行中に例外が発生した場合、プログラムが異常な状態でビジネスロジックを正常に処理できるようにするために、ここでは例外的に使用できます。例外コールバックを処理させるため。CompletionStageタスクで例外が発生すると、コールバックが例外的にトリガーされます。それ以外の場合、CompletionStageタスクの通常の実行では例外コールバック処理は実行されません。
public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);

// ====================================demo华丽分割线============================================
CompletableFuture<String> exceptionallyFuture = CompletableFuture.supplyAsync(() -> {
    int a = 0;
    int b = 10 / a;  // 除数为0将抛异常
    return "this is supplyAsync normal";
}).exceptionally(th -> {
    log.error("exception:{}", th.getMessage());
    return "this is exceptionally";
});
log.info(exceptionallyFuture.join());  // 输出结果:this is exceptionally
复制代码

:次の2つの状況は、実際の開発プロセスではまれな場合がありますが、最終的に不十分な準備による設計上の欠陥を回避するために、ここで注意する必要があります。

  • whenCompleteAsyncとexceptionalが同時に使用されている場合、例外が発生すると、exceptionalに戻り値があるため、whenCompleteAsyncが最初に実行され、次にexceptionalが実行されます。
  • handleAsyncとexceptionalが同時に表示される場合、handleAsyncにはすでに例外のすべての操作が含まれているため、つまり、handleAsyncコールバックには戻り値があり、例外を処理できるThrowable例外オブジェクトがあるため、2つが同時に、例外的に失敗します。

5.メソッドタイプの要約

上記の方法によると、これらのタスクは実際には3つのカテゴリに分類されます。これは、CompletionStageのコールバックメカニズムを介して、複数のタスク文字列、複数のタスクの並列、および複数のタスクの集約の操作を実現することに相当します。したがって、CompletableFuture非同期タスクに使用されます。処理により、より強力なプログラミングモデルが提供されます。したがって、java8によって提供されるCompletableFutureクラスは、元のFutureインターフェイスと比較して、いくつかのチェーンプログラミングを提供します。これにより、非同期タスクコールバック操作の多くの複雑な手順が不要になり、コードファーマーがより効率的に製品を出力できるようになります。

おすすめ

転載: juejin.im/post/7102277353614606344
おすすめ