Slide 1

Slide 1 text

JEP 480: Structured Concurrency 2024/09/12 JJUGナイトセミナー Java 23リリース記念イベント えばた あや @aya_122

Slide 2

Slide 2 text

自己紹介 - 名前: えばた あや - Twitter: @aya_122 - 好き: ラーメン二郎 / いくら🍣 / ポケモン - お仕事: フリーランス - Go / React / Android - 最近Flutterに入門した🤘

Slide 3

Slide 3 text

今日話すこと JEP 480: Structured Concurrency (Third Preview) https://openjdk.org/jeps/480

Slide 4

Slide 4 text

Structured Concurrencyとは - Java 23のJEP 480にてプレビュー機能として提案された - 並行プログラミングを簡素化するために導入

Slide 5

Slide 5 text

Structured Concurrencyとは - 異なるスレッドで実行される関連するタスクの グループを単一の作業単位として扱い、 エラー処理とキャンセルを合理化する

Slide 6

Slide 6 text

Structured Concurrencyとは - キャンセルやシャットダウンに起因する一般的なリスクを排除 - スレッドリークやキャンセル遅延など - 並行コードのobservabilityを向上させる - スレッドダンプを取った時、サブタスクのスレッドが何を しているかが見やすくなる

Slide 7

Slide 7 text

リリースの歴史 JDK 19 JEP 428: Incubator ↓ JDK 20 JEP 437: Second Incubator、JEP 429 ↓ JDK 21 JEP 435: Preview ↓ JDK 22 JEP 462: Second Preview ↓ JDK 23 JEP 480: Third Preview(今回の!)

Slide 8

Slide 8 text

並行処理の利点 - 通常のシングルスレッドでは、サブタスクが順次実行される - サブタスクを同時に実行することで、タスクをより高速に実行できる ようになる - 複数の処理が互いに独立している場合 - 十分なハードウェアリソースがある場合

Slide 9

Slide 9 text

今までの並行処理の書き方 - ExecutorServiceとFutureを使用することでサブタスクを同時に実行 try (ExecutorService esvc = Executors.newFixedThreadPool(2)) { Future user = esvc.submit(() -> findUser()); Future order = esvc.submit(() -> fetchOrder()); System.out.println(user.get()); System.out.println(order.get()); }

Slide 10

Slide 10 text

今までの並行処理の書き方の欠点 - 各サブタスクは独立して成功したり失敗したりする - 失敗すると、スレッドの寿命を理解するのが複雑になる場合がある - あるサブタスクが失敗した場合、別のサブタスクのキャンセルを 自動的に引き起こすことはない -> これがStructured Concurrencyで改善される -> 次のページで欠点の例を見ていくよ

Slide 11

Slide 11 text

今までの並行処理の書き方 欠点の例 - findUser()が例外を投げると、user.get()を呼び出すときに例外を スローし、fetchOrder()は自身のスレッドで実行し続けるが、結果は 取得できない try (ExecutorService esvc = Executors.newFixedThreadPool(2)) { Future user = esvc.submit(() -> findUser()); ⬅ 例外が発生 Future order = esvc.submit(() -> fetchOrder()); ⬅ 実行し続ける System.out.println(user.get()); ⬅ ここで例外がスローされる System.out.println(order.get()); ⬅ すでにエラーで落ちて取得できない }

Slide 12

Slide 12 text

今までの並行処理の書き方 欠点の例 - findUser()の実行に時間がかかりその間にfetchOrder()が 失敗した場合、findUser()の完了を待った後にorder.get()が例外を スローする try (ExecutorService esvc = Executors.newFixedThreadPool(2)) { Future user = esvc.submit(() -> findUser()); ⬅ 長い処理 Future order = esvc.submit(() -> fetchOrder()); ⬅ 例外が発生 System.out.println(user.get()); ⬅ findUser()終了後、値が取得できる System.out.println(order.get()); ⬅ 例外がスローされる }

Slide 13

Slide 13 text

StructuredTaskScopeクラスとは - java.util.concurrentパッケージのStructuredTaskScopeが Structured Concurrency APIの主要なクラス - StructuredTaskScopeを使うと、サブタスクの成功した結果や 例外が集約され、親タスクによって処理される - サブタスクのライフタイムをレキシカルスコープで限定し、その スコープ内でタスクとサブタスクのすべてのやり取り(フォーク、 結合、キャンセル、エラー処理、結果の合成)が行われる

Slide 14

Slide 14 text

Structured Concurrencyでの書き方 try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Supplier user = scope.fork(() -> findUser()); Supplier order = scope.fork(() -> fetchOrder()); scope.join().throwIfFailed(); System.out.println(user.get()); System.out.println(order.get()); }

Slide 15

Slide 15 text

Structured Concurrencyでの書き方 1. StructuredTaskScope.ShutdownOnFailure()でスコープを作成する - サブタスクのいずれかが失敗した場合、もう一方のサブタスクが キャンセルされる - try-with-resourcesでスコープが閉じられると、サブタスクの 全てのスレッドは終了する try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { // ... }

Slide 16

Slide 16 text

Structured Concurrencyでの書き方 2. fork()メソッドを使用して、新しいスレッドを作成する - 型はFutureではなくSupplierになる Supplier user = scope.fork(() -> findUser()); Supplier order = scope.fork(() -> fetchOrder());

Slide 17

Slide 17 text

Structured Concurrencyでの書き方 3. join()メソッドですべてのサブタスクを1つの ユニットとして結合し、すべてのサブタスクが 成功するか、キャンセルされるまで待つ - throwIfFailed()メソッドは最初に失敗した サブタスクの例外をスローする scope.join().throwIfFailed();

Slide 18

Slide 18 text

Structured Concurrencyでの書き方 5. 結合後、get()メソッドでその結果を返す System.out.println(user.get()); System.out.println(order.get());

Slide 19

Slide 19 text

shutdown()メソッド - スコープのオーナー、スコープ内のサブタスク、ネストされた スコープ内のサブサブタスクは、いつでもshutdown()メソッドを 呼び出してタスクを終了できる - shutdown()メソッドの呼び出し後にフォークされた新しいサブタスク はUNAVAILABLE状態になり実行されない

Slide 20

Slide 20 text

joinUntil()メソッド - joinUntil()メソッドを使うと指定した期限まで待つことができる - サブタスクが終了するか、shutdown()メソッドが呼び出される前に joinUntil()メソッドの期限が切れると、例外がスローされる

Slide 21

Slide 21 text

シャットダウンポリシー - StructuredTaskScope.ShutdownOnFailure()はサブタスクが失敗 した時にスコープをシャットダウンするポリシー - StructuredTaskScope.ShutdownOnSuccess()はサブタスクが成功 した時にスコープをシャットダウンするポリシー - 継承してオーバーライドするとカスタマイズ可能

Slide 22

Slide 22 text

ShutdownOnSuccess()を使う場合 - result()メソッドは最初に成功したサブタスクの結果を返す try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) { scope.fork(() -> findUser()); scope.fork(() -> fetchOrder()); System.out.println(scope.join().result()); }

Slide 23

Slide 23 text

まとめ - StructuredTaskScopeクラスを使用してスレッドを立てると - 一般的なリスクを排除できる - 一方のサブタスクが成功/失敗をすると、残りのサブタスクを キャンセルできる - サブタスクが成功した時、失敗した時など、処理がわかりやすくなる