一緒に書く習慣を身につけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して22日目です。クリックしてイベントの詳細をご覧ください。
ビーコンの火災は3か月続き、家族からの手紙は1万ゴールドに達しました。
1はじめに
プロジェクト開発では、非同期処理は問題を解決するための非常に一般的な方法です。スレッドプールを使用することに加えて、非同期処理はCompletableFuture
を使用して実装する。マルチタスクの場合、それらの間に論理的な関係があり、それを反映できます。大きな利点と柔軟性。CompletableFuture
最下層はForkJoinPool
スレッドプールを使用して、スレッドの実行とスケジューリングを実装します。
2使いやすい
スレッドプールを使用する場合、通常の使用法は次のとおりです。
ExecutorService service = Executors.newFixedThreadPool(3);
Callable<String> task1 = () ->{return "task1";};
Runnable task2 = () ->{
System.out.println("task2 ");
};
// 用于提交任务根据是否获取返回值分为 Callable 和 Runnable,分别使用 submit 和 execute 方法
service.submit(task1);
service.execute(task2);
复制代码
ただしCompletableFuture
、では、使用方法はまだ異なります。これは、スレッドプールとタスクの組み合わせであり、チェーンプログラミングを使用してタスク間の論理関係を処理できます。具体的な用途は次のとおりです。
// 使用默认线程池
CompletableFuture<String> async1 = CompletableFuture.supplyAsync(() -> {
log.info("async1 ... ");
return "async1";
});
// 使用自定义线程池
CompletableFuture<String> async1 = CompletableFuture.supplyAsync(() -> {
log.info("async1 ... ");
return "async1";
}, Executors.newSingleThreadExecutor());
// runAsync 的使用方式
CompletableFuture<Void> future = CompletableFuture.runAsync(()-> {
System.out.println("runAsync");
});
复制代码
非同期タスクを開くには、一般に2つの方法がありsupplyAsync
、runAsync
これら2つの方法の違いは次のとおりです。
- 1 supplyAsyncは入力パラメーターを受け入れませんが、結果を返します。
- 2 runAsyncも入力パラメーターを受け入れませんが、結果を返しません。
ここで最初に説明する必要があります。xxxAsyncのメソッドはスレッドプールからスレッドを取得してタスクを処理し、Asyncを終了しないメソッドは前のタスクのスレッドを使用して処理を続行します。
3非同期処理
正式に始める前に、java8関数型プログラミングの機能について説明する必要があります。多くの関数を見ると誰もがめまいがするだろうと思いますが、従うべきルールもあります。3つの主要なものについて話しましょう。
- 1関数、関数なので、計算に使用できる入力パラメータと戻り値があります。
- 2コンシューマーは、入力パラメーターを受け取るが戻り値を持たないコンシューマーであり、消費にのみ使用されます。
- 3サプライヤーは、パラメーターを受け入れないプロバイダーですが、オブジェクトの作成に使用できる戻り値を持っています。
- 4判断に使用される述語は、入力パラメーターを受け取り、戻り値はブール型(trueまたはfalse)です。
有这基本的 4 个,就可以进行延伸了,比如 IntFunction 则是接收一个 int 类型的参数,处理完成后即可返回,前面的 Int 只是规定了入参的类型而已,再有 BiConsumer , 则是接收两个入参,Consumer 则是只能接收一个参数。依次类推就可以知道所有的函数式接口的功能,是不是很简单?
3.1 thenApply
thenApply 和 thenApplyAsync 都是接收一个 Function 参数,即接收一个参数并返回结果。区别在于前者是使用前一个任务的线程继续处理,后者是从线程池中在获取一个线程处理任务。
如上图所示,thenApply 的任务处理和 future 使用的是一个线程,但是 thenApplyAsync 就换了一个线程继续数据的处理。
3.2 thenAccept 和 thenRun
从方法名可以看到 thenAccept 和 thenRun 都是使用前一个人任务的线程进行处理的。两者都是在前一个任务完成后进行处理,区别点在于 thenAccept 接收的是一个 Consumer , 而 thenRun 接收的是一个 Runnable, 因此两者都没有返回值,但是前者可以接收并消费一个参数,但是 thenRun 不能接收参数。这两个方法的测试如下图所示:
既然这两个方法已经搞清楚了,那么 thenAcceptAsync 和 thenRunAsync 是不是就顺手学到了呢?异步编程的 API 真的是很简单。
3.3 exceptionally 异常处理
exceptionally 属于异常处理流程,如果发生异常则需要进行异常处理,需要将异常最为参数传递给 exceptionally, 而其需要的是一个 Function 参数,这里的异常处理也是同步进行的,也是采用上一个任务的线程进行处理。
// 抛出异常信息
CompletableFuture<String> exceptionally = future.exceptionally((ex) -> {
log.info("error information " + ex.getLocalizedMessage());
return ex.getMessage();
});
复制代码
3.4 whenComplete 方法完成之后
这个方法是当某个任务执行完成之后进行回调,会将任务的执行结果或者执行期间的异常信息传递过来进行处理,在正常的情况下,异常信息为 null,能够得到任务的运算结果,异常情况下,异常信息不为空,返回结果为 null。这里的 whenComplete 接受的是一个 BiConsumer 函数,也就是两个入参,没有返回结果,一个是方法的返回结果,一个则是任务处理过程中的异常信息。
// 返回结果
CompletableFuture<String> whenComplete = future.whenComplete((res, ex) -> {
if (StrUtil.isNotBlank(res)) {
log.info("task execute result {}", res);
}
if (res != null) {
log.info("task error info {}", ex.getMessage());
}
});
复制代码
知道了 whenComplete 方法,那么 whenCompleteAsync 方法的使用就知道了,就是异步处理了。
3.5 handle
handle 的使用和 whenComplete 方法类似,都是获取任务的结果,只不过 handle 有返回结果,接受的参数是一个 BiFunction ,那么具体的使用方法如下图所示:
// handle 处理返回结果
CompletableFuture<String> handle = future.handle((res, ex) -> {
if (StrUtil.isNotBlank(res)) {
log.info("task execute result {}", res);
return "handle result exception";
}
if (res != null) {
log.info("task error info {}", ex.getMessage());
}
return "handle result";
});
复制代码
通过以上的分析,我们已经到得了以下规律:任何一个方法的实现都有三个类似的 API,一个是同步处理,一个是异步处理,一个是异步处理并指定线程池参数。目前已经介绍了 6 个 API,分别是 thenApply
, thenAccept
,thenRun
, whenComplete
, handle
和 一个异常处理 exceptionally
, 前五个举一反三就知道了其他的两个异步调用 API,掌握了其中的规律就不会觉得很多,无非就是同步异步,是否接收参数和有无返回值的区别。
4 处理组合
4.1 任务均完成后组合
thenCombine
、thenAcceptBoth
、runAfterBoth
这三个方法都是在两个 CompletableFuture
任务结束后在进行执行,区别在于是否接受参数以及是否有返回值,如图所示查看其接受的参数。
- 1
thenCombine
方法为两个,第一个为CompletionStage
对象即另一个异步任务,第二个为BiFunction
,接收两个任务的处理结果并返回处理结果。 - 2
thenAcceptBoth
方法为两个,第一个为CompletionStage
对象即另一个异步任务,第二个为BiConsumer
, 接收两个任务的处理结果不过没有返回值。 - 3
runAfterBoth
方法为两个,第一个为CompletionStage
对象即另一个异步任务,第二个为Runnable
,不接收两个任务的处理结果,也没有返回值。
下图是方法的使用案例: 既然知道了这些方法的用法,那么 thenCombineAsync
、thenAcceptBothAsync
、runAfterBothAsync
是不是就可以同理掌握了呢?
4.2 任一任务完成
前文提到的都是两个任务均完成的情况,接下来的三个方法则是任何一个任务完成即可执行下一个动作,applyToEither
、acceptEither
、runAfterEither
这三个方法都是在两个异步任务执行结果之后的处理,任何一个任务执行完毕之后就进行继续处理。
这里的任一任务执行完成和两者任务都执行完在执行是类似的,区别在于这里接收的是一个参数:
- 1 applyToEither 接收的参数是 CompletionStage 和 Function。
- 2 acceptEither 接收的参数是 CompletionStage 和 Consumer。
- 3 runAfterEither 接收的参数是 CompletionStage 和 Runnable。
这里已经学习到了 applyToEither
、acceptEither
、runAfterEither
三个方法,那么类似的 applyToEitherAsync
、acceptEitherAsync
、runAfterEitherAsync
也可以知道其具体用法。
4.3 任务处理结果
thenCompose
の使用法は基本的にthenCombine
etcなど、戻りパラメータに違いがあり、結果はFutureを返し、入力パラメータは関数になります。thenCompose
理解した後thenComposeAsync
、使用法は似ています。
CompletableFuture<String> thenCompose = future.thenCompose((res) -> {
log.info("result is {}", res);
return CompletableFuture.supplyAsync(() -> {
log.info("supplyAsync");
return "result";
});
});
复制代码
4.4すべてまたはいずれか
2つのタスクと1つのタスクを処理した後、すでに操作を共有しています。このセクションallOf
でanyOf
は、複数のタスクの集約処理であるandを共有し、複数の入力パラメータがあります。CompletableFuture
違いは、タスクが完了した後、実行することです。後続のタスク、またはすべてのタスクが完了した後にタスク処理を続行します。その使用法は次のとおりです。
CompletableFuture<Void> allOf = CompletableFuture.allOf(future);
CompletableFuture<Object> andOf = CompletableFuture.anyOf(future);
复制代码
5まとめ
この記事では、最初に関数型プログラミングのインターフェースの使用法を紹介し、次にCompletableFuture
のます。コアは関数型プログラミングインターフェイスであり、受信はFunction
、、Consumer
またはRunable
、そして次に、xxxAsync
非同期です。