記事ディレクトリ
CompletableFuture は Future の拡張バージョンであり、マルチスレッド開発のための強力なツールです。この記事では、CompletableFuture の使い方を分かりやすく紹介し、最後に、すぐに使用できて品質も信頼できる CompletableFuture の一般的な使用パラダイムを紹介します。
1. CompletableFutureとは
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
}
- Future:非同期計算の結果を表します
- CompletionStage:非同期計算の段階を表し、複雑な計算を表します。
上記の定義から、CompletableFuture は Future インターフェイスを実装するだけでなく、Future 関数の拡張機能である CompletionStage インターフェイスも実装していることがわかります。Java 8から導入されCompletableFuture
、Future
コールバックオブジェクトを渡すことができるように改良され、非同期タスクの完了時や例外発生時にコールバックオブジェクトのコールバックメソッドが自動的に呼び出されます。
2. CompletableFuture が必要な理由
を使用してFuture
非同期実行結果を取得する場合は、ブロッキング メソッドを呼び出すか、ブロッキング メソッドであるかどうかget()
をポーリングする必要があります。メイン スレッドも待機することになるため、どちらの方法もあまり適切ではありません。isDone()
true
CompletableFuture は、非同期コールバックや複雑な計算のサポートが必要な場合にも役立ちます。
3. CompletableFuture を使用する
CompletableFuture クラスには多くのメソッドがあり、単純に記憶するのは非常に面倒なので、分類する必要があります
クラスの作成
-
SupplyAsync: 非同期実行、戻り値
-
runAsync: 非同期実行、戻り値なし
-
anyOf: 実行が完了すると、次のステップを実行できます。
-
allOf: 次のタスクに進む前にすべてのタスクを完了します。
// 返回一个0
CompletableFuture.supplyAsync(() -> 0);
継続クラス (thenXxx)
-
継続クラスは CompletableFuture の最も重要な機能であり、これがなければ CompletableFuture は意味がなく、コールバック動作を挿入するために使用されます。
-
Java 8 関数インターフェイスには、Function、Supplier、Consumer、Runnable の 4 つの一般的なタイプがあることがわかっています。偶然ですが、CompletableFuture の再開メソッドもこれら 4 つのタイプのインターフェイスをサポートしています。1 対 1 の対応関係を次の表に示します。 :
Java 8 機能インターフェイス | CompletableFutureの継続メソッド | 説明する |
---|---|---|
関数 (パラメータと戻り値を含む) | thenApply メソッド | thenApplyAsync を使用しない非同期 |
サプライヤー (パラメータなしで返されます) | supplyAsync方法,runAsync方法 | CompletableFuture の作成時に使用されます |
コンシューマ (パラメータあり、戻りなし) | thenAccept メソッド | thenAcceptAsync を使用しない |
実行可能 (パラメータなし、戻りなし) | thenRunメソッド | thenRunAsync 非同期を使用しない |
- thenXxx メソッドを使用するだけで、Async サフィックスが付いた非同期メソッドを使用する必要があります。後のステップは前のステップの結果に依存するため、
// 以异步计算1+2+3为例
CompletableFuture.supplyAsync(() -> {
return 0;
}).thenApply(v -> {
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {
e.printStackTrace(); }
return v + 1;
}).thenApply(v -> {
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {
e.printStackTrace(); }
return v + 2;
}).thenApply(v -> {
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {
e.printStackTrace(); }
return v + 3;
});
System.out.println("main线程可以异步干点别的事情,不用等待计算完成");
4. CompletableFuture を使用するための一般的なパラダイム
一般に、CompletableFuture を使用して非同期マルチスレッドを使用してクエリを高速化しますが、書き込み操作よりも読み取り操作の方が多いため、複数の CompletableFuture オブジェクトを作成する必要があります。
マイクロサービスアーキテクチャにおいて、ユーザーの注文と配送先住所を一度に返す必要があり、その情報はそれぞれ注文マイクロサービスと配送先マイクロサービスにあるとします。ユーザーに迅速に応答するには、次のいずれかを選択する必要があります。複数のスレッドを開いて同時にクエリを実行し、2 つのマイクロサービスが最終的にクエリ結果を結合してユーザーに返します。
一般に、次の CompletableFuture の一般的なパラダイムをそのまま使用し、コピーして貼り付けることができます。
Result result = new Result();
// <1> 查询订单
CompletableFuture<List<Order>> orderCompletableFuture = CompletableFuture.supplyAsync(() -> {
// <1.1> http查询订单
List<Order> orderList = queryOrderList(uid);
// <1.2> 设置结果集
result.setOrderList(orderList);
return orderList;
})
// <2> 后续处理
.thenApply(v -> {
System.out.println("此处可以继续处理...");
return v;
});
CompletableFuture<List<Address>> addressCompletableFuture = CompletableFuture.supplyAsync(() -> {
List<Address> addressList = queryAddressList(uid);
result.setAddressList(addressList);
return addressList;
});
// <3> 等待所有任务执行完成
CompletableFuture.allOf(orderCompletableFuture, addressCompletableFuture);
// <4> 记录异常信息并抛出
List<String> errMessage = new ArrayList<>();
List<CompletableFuture<?>> completableFutures = Arrays.asList(whenComplete, whenComplete1);
for (int i = 0; i < completableFutures.size(); i++) {
CompletableFuture<?> future = completableFutures.get(i);
int finalI = i;
future.exceptionally(ex -> {
String str = "列表位置索引" + finalI + "处发生异常,异常信息是" + ex.getMessage();
errMessage.add(str);
throw new RuntimeException("Error occurred: " + ex);
});
}
// <5> 异常处理
if(CollUtil.isNotEmpty(errMessage)){
log.error("计算过程发生异常,异常有{}处,异常详细信息是:{}", errMessage.size(), errMessage);
throw new RuntimeException("存在异常,可能有多处请逐个排查,异常信息列表是" + errMessage);
}
// <6> 返回结果给用户
return result;
-
ここでは
<1>
、非同期でリターンするメソッドが使用されていますCompletableFuture.supplyAsync
。これは最も一般的に使用されるメソッドです。SupplyAsync メソッドは非同期で戻り値がありますが、runAsync は非同期で戻り値がありません。supplyAsync メソッドの方が多用途です。選択するだけで完了です。
- ここでは
<1.1>
、RestTemplate やリボンなどを介して注文マイクロサービスから注文をクエリおよび解析できます。 - で
<1.2>
、順序情報を結果セットに設定します
- ここでは
-
ここで
<2>
、前のステップからの情報をさらに処理できます。オプション。ここでは thenApply、thenAccept、thenRun などのメソッドを使用できます。
前のステップの結果に依存するため、ここではthenXxxAsync 非同期メソッドを使用する代わりにthenXxx メソッドを使用することをお勧めします。
-
で
<3>
、注文クエリと住所クエリが完了するまで待ちます。 -
で
<4>
、例外をログに記録し、例外を再スローします。必要。ログは記録する必要があり、例外が記録されないと、例外情報が失われます。
-
at
<5>
、例外を再スローするかどうか。オプション。場合によっては、個別のインターフェイス例外を受け入れることができ、例外をスローする必要はありませんが、例外はログに記録する必要があります。
-
で
<6>
、注文と住所の結果セットをユーザーに返します。