Slide 1

Slide 1 text

The rest of "Loom" Structured concurrency and Scoped Values Virtual threads 以外の “Project Loom” NISHIKAWA, Akihiro Cloud Solution Architect, Microsoft Japan

Slide 2

Slide 2 text

Project Loomの目的 “Project Loom is to intended to explore, incubate and deliver Java VM features and APIs built on top of them for the purpose of supporting easy-to-use, high-throughput lightweight concurrency and new programming models on the Java platform. “ Main - Main - OpenJDK Wiki Javaプラットフォーム上で使いやすく、高スループットの軽量な並行処理と新しいプログラミングモデルをサポートするための、 Java VMの機能とAPIを提供することを目指したプロジェクト

Slide 3

Slide 3 text

Project Loom  Virtual Threads  JEP 444: Virtual Threads (openjdk.org)  Structured Concurrency (Preview)  JEP 453: Structured Concurrency (Preview) (openjdk.org)  Scoped Values (Preview)  JEP 446: Scoped Values (Preview) (openjdk.org)

Slide 4

Slide 4 text

今日のスコープ  Virtual Threads  JEP 444: Virtual Threads (openjdk.org)  Structured Concurrency (Preview)  JEP 453: Structured Concurrency (Preview) (openjdk.org)  Scoped Values (Preview)  JEP 446: Scoped Values (Preview) (openjdk.org)

Slide 5

Slide 5 text

Virtual Threads JEP 444: Virtual Threads (openjdk.org)

Slide 6

Slide 6 text

Virtual Threadsと並列・並行処理については... Virtual Threads - 導入の背景と、効果的 な使い方 - - Speaker Deck Javaの並列/並行処理の基本 - Speaker Deck A 007 Virtual Threads 導入の背景と、効果的な使い方 - YouTube Java仕様勉強会「Javaの並列/並行処理」 - YouTube

Slide 7

Slide 7 text

Structured Concurrency (Preview) JEP 453: Structured Concurrency (Preview) (openjdk.org)

Slide 8

Slide 8 text

これって何? 異なるスレッドで実行される関連タスクのグループを単一の作業単位として扱うこ とにより、以下を実現することを目指す  エラー処理やキャンセルのしくみをシンプルに  信頼性の向上  Observabilityの向上 Kotlin (coroutine)、Swift、Python (Trio)、Scalaなどで実装済み

Slide 9

Slide 9 text

経緯  JEP 428 (JDK 19)  Incubator  JEP 437 (JDK 20)  Second Incubator  Scoped Valuesを継承するようにアップデート  JEP 453 (JDK 21)  java.util.concurrentパッケージのPreview API  StructuredTaskScope::fork(...)メソッドの戻り値をFutureからSubtaskに変更

Slide 10

Slide 10 text

目指していること・いないこと 目指していること  スレッドリークやキャンセル遅延など、キャン セルやシャットダウンに起因する一般的な リスクを排除できる並行プログラミングのス タイルを推進する  並列プログラムコードのObservabilityを向 上 目指していないこと  java.util.concurrentパッケージの 並行処理コンストラクトの置き換え  Java Platform用の決定的な構造化並 行APIの定義  スレッド間でデータのストリームを共有する 手段(つまりチャネル)の定義(現段階 では)  既存のスレッド中断メカニズムから新しいス レッドキャンセル機構への置き換え(現段 階では)

Slide 11

Slide 11 text

並列処理 Subtask 1 Subtask 2 Subtask 3 Single thread Subtask 1 Subtask 2 Subtask 3 Multi threads リソースがあるなら、並行実行によりタスク全体の処理時間は 小さくなるが、スレッドの管理やトラブルシューティングが大変 (Subtask2が落ちたら他のSubtaskはどうすれば...) サブタスクは順次実行するので追跡は簡 単だが、タスク全体の処理時間は大きく なる

Slide 12

Slide 12 text

java.util.concurrent.ExecutorService API // ExecutorServiceは作成済み private final ExecutorService esvc = ...; Response handle() throws ExecutionException, InterruptedException { Future user = esvc.submit(() -> findUser()); Future order = esvc.submit(() -> fetchOrder()); // findUserの終了待ち String theUser = user.get(); // fetchOrderの終了待ち int theOrder = order.get(); return new Response(theUser, theOrder); } futureのget()メソッドでサブタスクの結果 を待っている(ブロッキング呼び出し)

Slide 13

Slide 13 text

サブタスクが失敗したときの挙動 findUser()が 例外をスローした場合 • handle()はuser.get()を呼び出すときに例外をスローする • fetchOrder()はそれ自身のスレッドで実行し続ける handle()を実行している スレッドが中断された場合 • 中断はサブタスクに伝搬しない • findUser()とfetchOrder()の両スレッドはリークする • handle()が失敗した後も実行を継続する findUser()の実行に長 い時間がかかり、その間に fetchOrder()が失敗し た場合 • handle()はfindUser()をキャンセルせずuser.get()をブロックし、 不必要にfindUser()を待つ • findUser()が完了し、user.get()が返された後にのみ、 order.get()が例外をスローし、handle()が失敗する

Slide 14

Slide 14 text

ExecutorServiceとFuture  ExecutorServiceとFutureは無制限の同時実行パターンを許容  現在の仕組みでは、タスク間の構造を使い、サブタスクのライフタイムの管理自 動化が難しく、手作業でライフタイムを調整する必要がある

Slide 15

Slide 15 text

Single threadのコードでは  常にタスクとサブタスクの階層構造が強制される  タスクとサブタスクの階層は実行時にコールスタックに再定義される Response handle() throws IOException { String theUser = findUser(); int theOrder = fetchOrder(); return new Response(theUser, theOrder); }

Slide 16

Slide 16 text

並列・並行処理時にも同じようにできないか?  実現できれば、並行処理がより簡単、より信頼性高く、監視もしやすくなる  構文構造によってサブタスクの寿命を明確にできる  スレッド内コールスタックに類似したスレッド間階層の実行時表現が可能  この表現を使えば、  エラー伝播とキャンセルが可能  並行プログラムの監視も容易になる(はず) 並行タスクに構造を課すためのAPI (java.util.concurrent.ForkJoinPool) はすでに存在するものの、I/Oを伴うタスクではなく、計算集約的なタスクのために設計されて いるもので、使い勝手が悪い

Slide 17

Slide 17 text

Structured Concurrency  タスクとサブタスクの自然な関係を維持する並行プログラミングのアプローチ  より読みやすく、保守性が高く、信頼性の高い並行コードを実現  Martin Sústrik  250bpm  Nathaniel J. Smith  Notes on structured concurrency, or: Go statement considered harmful — njs blog (vorpus.org)

Slide 18

Slide 18 text

原理 “If a task splits into concurrent subtasks, then they all return to the same place, namely the task's code block.” (タスクが並行サブタスクに分割されたら、それらはすべて同じ場所、つまりタスクの コードブロックに戻る) そのために 1. コードのブロックを流れる実行の入口と出口を明確に定義されなければならない 2. コードの構文上の入れ子構造を反映するように、操作のライフタイムが厳密に入れ子構造になっている必要 がある

Slide 19

Slide 19 text

StructuredTaskScope java.util.concurrentパッケージ  タスクをサブタスクのファミリーとして構造化  サブタスクはそれぞれのスレッドで実行  個々にフォーク  ユニットとしてjoin、キャンセル  サブタスクの結果や例外は集約され、親タスクが処理  サブタスクのライフタイム  明確なレキシカル・スコープに限定  そのライフタイム中に、タスクとサブタスクのすべてのやり取りを行う (フォーク、結合、キャンセル、エラー処理、 結果の合成)

Slide 20

Slide 20 text

java.util.concurrent.StructuredTaskScope API public class StructuredTaskScope implements AutoCloseable { public Subtask fork(Callable extends U> task); public void shutdown(); public StructuredTaskScope join() throws InterruptedException; public StructuredTaskScope joinUntil(Instant deadline) throws InterruptedException, TimeoutException; public void close(); protected void handleComplete(Subtask extends T> handle); protected final void ensureOwnerAndJoined(); }

Slide 21

Slide 21 text

StructuredTaskScopeでhandleメソッドを書き換え Response handle() throws ExecutionException, InterruptedException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Supplier user = scope.fork(() -> findUser()); Supplier order = scope.fork(() -> fetchOrder()); // 両サブタスクを待ち受け、エラー発生時には例外を投げる scope.join().throwIfFailed(); // 両サブタスクが成功したら、両者の結果をまとめる return new Response(user.get(), order.get()); } } 元のhandle()

Slide 22

Slide 22 text

StructuredTaskScopeでhandleメソッドを書き換え Response handle() throws ExecutionException, InterruptedException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Supplier user = scope.fork(() -> findUser()); Supplier order = scope.fork(() -> fetchOrder()); // 両サブタスクを待ち受け、エラー発生時には例外を投げる scope.join().throwIfFailed(); // 両サブタスクが成功したら、両者の結果をまとめる return new Response(user.get(), order.get()); } } ① スレッドのライフタイムはtry-with-resources内に限定 ② findUser()サブタスクまたはfetchOrder()サブタスクのいずれかが失敗した場合、それがまだ完了していな ければ、もう一方のサブタスクはキャンセル (ShutdownOnFailureのシャットダウン・ポリシーによる) ③ join()の呼び出しの前または呼び出し中にhandle()を実行しているスレッドが割り込まれた場合、そのスレッド がスコープを抜けると、両方のサブタスクが自動的にキャンセル ④ thread dumpはタスク階層を明確に表す 【例】findUser()とfetchOrder()を実行しているスレッドはスコープの子として表示 ① ②

Slide 23

Slide 23 text

通常はtry-with- resourcesで暗黙 的にスコープを閉じる スコープ内でjoin()または joinUntil(java.time.Instan t)の呼び出しは必須 任意のタイミングでス コープの shutdown()メソッド を呼び出せる fork(Callable )メソッドを使用 スコープの終了 エラー処理や 結果の集計 スコープオーナー がスコープのjoin を指示 (Optional) 未完サブタスク のキャンセル、 新サブタスクの フォーク抑止 スコープ内の サブタスクを フォーク StructuredTaskScopeを使用する場合の流れ スコープを作成した スレッドがスコープの オーナー スコープの作成

Slide 24

Slide 24 text

シャットダウンポリシー ShutdownOnFailure ShutdownOnSuccess 最初のサブタスクが失敗したときにShutdown (すべてのサブタスクが成功することを期待) サブタスクの一つが成功したときにShutdown (どれか一つが成功することを期待) カスタムシャットダウンポリシーを作成することも可能

Slide 25

Slide 25 text

shutdown-on-failureポリシーを持つStructuredTaskScope List runAll(List> tasks) throws InterruptedException, ExecutionException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { List extends Supplier> suppliers = tasks.stream().map(scope::fork).toList(); scope.join() .throwIfFailed(); // サブタスクが失敗すると例外を伝播 // すべてのタスクが成功したら、結果をまとめる return suppliers.stream().map(Supplier::get).toList(); } }

Slide 26

Slide 26 text

shutdown-on-successポリシーを持つStructuredTaskScope T race(List> tasks, Instant deadline) throws InterruptedException, ExecutionException, TimeoutException { try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) { for (var task : tasks) { scope.fork(task); } // 一定期間まで待機し、どのサブタスクも成功しなければ例外を送出 return scope.joinUntil(deadline) .result(); } }

Slide 27

Slide 27 text

カスタムシャットダウンポリシー  必要であれば、ShutdownOnSuccess、ShutdownOnFailure以外のポ リシーを実装できる  StructuredTaskScopeを拡張し、protected handleComplete(...)メソッドをオーバーライド public sealed interface Subtask extends Supplier { enum State { SUCCESS, FAILED, UNAVAILABLE, } State state(); Callable extends T> task(); T get(); Throwable exception(); }

Slide 28

Slide 28 text

カスタムシャットダウンポリシー  例えば  正常に完了したサブタスクの結果を収集し、失敗したサブタスクを無視したい  サブタスクが失敗したときに例外を収集したい  shutdown()メソッドを呼び出してシャットダウンし、何らかの条件が発生したときにjoin()が動作するよう にしたい

Slide 29

Slide 29 text

正常に完了したサブタスクの結果を収集するStructuredTaskScopeサブクラスの例 class MyScope extends StructuredTaskScope { private final Queue results = new ConcurrentLinkedQueue<>(); MyScope() { super(null, Thread.ofVirtual().factory()); } @Override protected void handleComplete(Subtask extends T> subtask) { if (subtask.state() == Subtask.State.SUCCESS) results.add(subtask.get()); } @Override public MyScope join() throws InterruptedException { super.join(); return this; } // 成功完了したサブタスクからの結果ストリームを返す public Stream results() { super.ensureOwnerAndJoined(); return results.stream(); } } // 使い方 List allSuccessful(List> tasks) throws InterruptedException { try (var scope = new MyScope()) { for (var task : tasks) scope.fork(task); return scope.join() .results().toList(); } }

Slide 30

Slide 30 text

スコープオーナーによる結果の処理 シャットダウンポリシーがサブタス クの結果を処理しない場合 シャットダウンポリシーがサブタス クの結果を処理する場合 シャットダウンポリシーを使わず、 サブタスクの例外を処理して複 合した結果を生成する場合 • fork(...)で返されたSubtaskオブジェクトを使い、サブタスク の結果を処理 • スコープオーナーが呼び出すSubtaskメソッド: ほとんどの場合、 get()メソッドのみ • fork(...)メソッドはvoidを返すものとして扱う • サブタスクは、ポリシーによる集中例外処理後にスコープオーナーが 処理すべき情報を結果として返す • 例外はサブタスクからの値として返すことができる • たとえば、タスクのリストを並列に実行し、各タスクの成功または例 外の結果を含む、完了したFutureのリストを返すことも可能

Slide 31

Slide 31 text

タスクのリストを並列に実行し、各タスクの成功または例外の結果を含む、完了したFutureのリストを返すメソッド List> executeAll(List> tasks) throws InterruptedException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { List extends Supplier>> futures = tasks.stream() .map(task -> asFuture(task)) .map(scope::fork) .toList(); scope.join(); return futures.stream().map(Supplier::get).toList(); } } static Callable> asFuture(Callable task) { return () -> { try { return CompletableFuture.completedFuture(task.call()); } catch (Exception ex) { return CompletableFuture.failedFuture(ex); } }; }

Slide 32

Slide 32 text

Fan-inシナリオ  外部からのTCPソケットを受け付ける例  Taskの生存期間は事前に決まっていない  Subtaskの個数も事前に決まっていない (動的に変化するため)  これまでの例 (Fan-outシナリオ) だと...  Taskの生存期間は決まっている  Subtaskの個数も決まっている Subtask Subtask Subtask Subtask Task Peer

Slide 33

Slide 33 text

StructuredTaskScope内でサブタスクをフォークし、着信接続を処理するサーバーの例 void serve(ServerSocket serverSocket) throws IOException, InterruptedException { try (var scope = new StructuredTaskScope()) { try { while (true) { var socket = serverSocket.accept(); scope.fork(() -> handle(socket)); } } finally { // エラーもしくは割り込み発生時には、Acceptを停止 scope.shutdown(); // 全アクティブ接続をクローズ scope.join(); } } }

Slide 34

Slide 34 text

Observability  StructuredTaskScopeスレッドをグループ化、階層化したthread dump の取得  jcmdの利用  com.sun.management.HotSpotDiagnosticsMXBean APIの利用 jcmd Thread.dump_to_file -format=json

Slide 35

Slide 35 text

JEP 444 で追加された新しい JSON スレッドダンプ形式を拡張

Slide 36

Slide 36 text

fork()の戻り値がFutureからSubtaskに変わった理由  StructuredTaskScopeとExecutorServiceでは使い方が異なる  Futureオブジェクトは、join()後にクエリされるべきで、実質使えるメソッドFuture::resultNow() のみ  CompletableFutureオブジェクトを返さない理由  CompletableFutureは非同期プログラミングパラダイム用に設計  StructuredTaskScopeはブロッキングパラダイムを推奨  Subtask::get()はFuture::resultNow()と全く同じように動作

Slide 37

Slide 37 text

Scoped Values (Preview) JEP 446: Scoped Values (Preview) (openjdk.org)

Slide 38

Slide 38 text

これって何? スレッド内およびスレッド間でImmutableなデータを共有できるようにするため、  ThreadLocalよりも扱いやすいしくみを導入する  Structured Concurrencyと組み合わせて利用できるようにする

Slide 39

Slide 39 text

経緯  JEP 429 (JDK 20)  Incubator  JEP 446 (JDK 21)  java.langパッケージのPreview API

Slide 40

Slide 40 text

目指していること・いないこと 目指していること  スレッド内と子スレッドの両方でデータを共 有するプログラミング・モデルを提供し、デー タ・フローに関する推論を単純化する  コードの構文構造から共有データの生存 期間が見えるようにする  呼び出し元が共有するデータを、正当な 呼び出し元のみが取得できるようにする  共有データをImmutableなものとして扱い、 多数のスレッド間の共有を可能にし、実 行時の最適化を可能にする 目指していないこと  Javaプログラミング言語の変更  ThreadLocal変数からの移行や、既存 のThreadLocal APIの非推奨化

Slide 41

Slide 41 text

ThreadLocal  Java 1.2以後で利用できる  スレッドに1つずつ  通常static finalフィールドとして宣言

Slide 42

Slide 42 text

ThreadLocalの課題 Mutable 無制限の ライフサイクル 高コストな 継承 • すべてのThread Local変数は変更可能 • どのコンポーネントがどのような順序で共有状態を更新しているのか追跡が大変 • 書き込んだデータはスレッドの寿命が尽きるまで、あるいはスレッドがremoveメソッ ドを呼び出すまで保持 • スレッドプール利用時、あるタスクで設定されたThreadLocal変数の値を適切 にクリアしなかったばかりに、無関係のタスクに誤ってリークしてしまう可能性... • ThreadLocal変数を継承する子スレッドでは、親スレッドで以前に書き込まれ たすべてのThreadLocal変数のストレージを割り当てる必要がある

Slide 43

Slide 43 text

•Virtual ThreadsでもThreadLocal変数を 持ち得る •もし大量に生成されたVirtual Threadsのそれ ぞれがmutableなThreadLocal変数を持っ たら... 🤔🤔

Slide 44

Slide 44 text

ライフタイム Virtual Threadごとに immutableで継承可能な データを維持する方法を提供 Scoped Value • スレッドごとにimmutableなので、データを子スレッドで効 率的に共有できる • 最初にデータを共有したメソッドが終了するまで利用可能 (終了したら利用できない)

Slide 45

Slide 45 text

使い方  基本的にはThreadLocalと同じように使える  immutable  スレッド実行中の限られた期間だけ使用できる

Slide 46

Slide 46 text

基本的な使い方 final static ScopedValue<...> V = ScopedValue.newInstance(); // whereでScoped Valuesとバインド対象のオブジェクトを提示 // runでScoped Valuesをバインド // run(...) が終了すると、バインディングは破棄 ScopedValue.where(V, ) .run(() -> { ... V.get() ... call methods ... }); // 直接的、もしくはラムダ式から間接的に呼び出されたメソッド内で値を取得可能 // set(...)メソッドは存在しないので、宣言時に書き込まれた値を取得できる ... V.get() ...

Slide 47

Slide 47 text

ThreadLocalからScopedValueへの置き換え - ThreadLocalの場合 class Server { final static ThreadLocal PRINCIPAL = new ThreadLocal<>(); void serve(Request request, Response response) { var level = (request.isAuthorized() ? ADMIN : GUEST); var principal = new Principal(level); PRINCIPAL.set(principal); Application.handle(request, response); } } class DBAccess { DBConnection open() { var principal = Server.PRINCIPAL.get(); if (!principal.canOpen()) throw new InvalidPrincipalException(); return newConnection(...); } }

Slide 48

Slide 48 text

ThreadLocalからScopedValueへの置き換え - ScopedValueの場合 class Server { final static ScopedValue PRINCIPAL = ScopedValue.newInstance(); void serve(Request request, Response response) { var level = (request.isAdmin() ? ADMIN : GUEST); var principal = new Principal(level); ScopedValue.where(PRINCIPAL, principal) .run(() -> Application.handle(request, response)); } } class DBAccess { DBConnection open() { var principal = Server.PRINCIPAL.get(); if (!principal.canOpen()) throw new InvalidPrincipalException(); return newConnection(...); } }

Slide 49

Slide 49 text

Rebinding scoped values  以下の条件では、ネストされた呼び出しに対し、ScopedValue APIは新しい バインドを確立できる  条件  同じスレッド内  呼び出し元が同じScoped Valuesを使用  別の値を呼び出し元に伝える必要がある

Slide 50

Slide 50 text

Rebinding scoped values (1/2) class Server { final static ScopedValue PRINCIPAL = ScopedValue.newInstance(); void serve(Request request, Response response) { var level = (request.isAdmin() ? ADMIN : GUEST); var principal = new Principal(level); ScopedValue.where(PRINCIPAL, principal) .run(() -> Application.handle(request, response)); } } class Logger { void log(Supplier formatter) { if (loggingEnabled) { var message = formatter.get(); write(logFile, "%s %s".format(timeStamp(), message)); } } } Logger.log(() -> { DBAccess.open();}) と記述できるが、formatter.get()では書式設定す るだけなので、不要な情報は渡したくない。

Slide 51

Slide 51 text

Rebinding scoped values (2/2) class Server { final static ScopedValue PRINCIPAL = ScopedValue.newInstance(); void serve(Request request, Response response) { var level = (request.isAdmin() ? ADMIN : GUEST); var principal = new Principal(level); ScopedValue.where(PRINCIPAL, principal) .run(() -> Application.handle(request, response)); } } class Logger { void log(Supplier formatter) { if (loggingEnabled) { var guest = Principal.createGuest(); var message = ScopedValue.where(Server.PRINCIPAL, guest) .call(() -> formatter.get()); write(logFile, "%s %s".format(timeStamp(), message)); } } } .call(() -> formatter.get()) の範囲でのみ、Server.PRINCIPALとして guestを使う

Slide 52

Slide 52 text

Scoped values as capabilities  Capability-based Security  ユーザアプリケーションを設計するためのコンセプトで、それらが「最小権限の原則」 (principle of least privilege) に基づいて直接 capability を分け合う方法でセキュリティを実現する方法  Scoped Valuesは、シンプルで堅牢なcapabilityの実装を提供  ScopedValueのオーナーは通常、private static finalのように適切にアクセス制限されたフィール ドで保護  ScopedValueオブジェクトは通常、広く共有されることはない(その必要もない)

Slide 53

Slide 53 text

ユーザー名が定義されているコンテキストでのみ、特定の操作実行を強制する runAsUser(...)に渡されたRunnableの内部 で実行されているコードだけが、doOperation() を呼び出せる public class Framework { private Framework() {} private static final Framework INSTANCE = new Framework(); public static void Framework instance() { return INSTANCE; } private static final ScopedValue USER = ScopedValue.newInstance(); public void runAsUser(String user, Runnable op) { ScopedValue.where(USER, user).run(op); } public void doOperation() { String user = USER.orElseThrow(() -> new IllegalStateException(“User not set”)); ... } }

Slide 54

Slide 54 text

Scoped Valuesの継承 Webフレームワークの場合 リクエスト処理スレッド(親スレッド)上でユーザーコードが動作 ユーザーコードでは、Virtual Threadsを利用可(Virtual Threadsが子スレッド) このとき、リクエスト処理スレッドで動作するコンポーネントが共有するデータを子スレッドでも利用できる必要が ある  親スレッドのScoped Valueは、StructuredTaskScopeで作成された子ス レッドに自動的に継承される  子スレッドのコードは、親スレッドのScoped Valuesに対して確立されたバインディングを最小限のオーバーヘッ ドで使用可  ThreadLocal変数とは異なり、親スレッドのScoped Valuesバインディングは子スレッドにコピーされない

Slide 55

Slide 55 text

Scoped Valuesの継承 (1/2) class Application { Response handle() throws ExecutionException, InterruptedException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Supplier user = scope.fork(() -> findUser()); Supplier order = scope.fork(() -> fetchOrder()); // 両Forkを待つ scope.join().throwIfFailed(); return new Response(user.get(), order.get()); } } String findUser() { ... DBAccess.open() ... } }

Slide 56

Slide 56 text

Scoped Valuesの継承 (2/2) class Server { final static ScopedValue PRINCIPAL = ScopedValue.newInstance(); void serve(Request request, Response response) { var level = (request.isAdmin() ? ADMIN : GUEST); var principal = new Principal(level); ScopedValue.where(PRINCIPAL, principal) .run(() -> Application.handle(request, response)); } } class DBAccess { DBConnection open() { var principal = Server.PRINCIPAL.get(); if (!principal.canOpen()) throw new InvalidPrincipalException(); return newConnection(...); } }

Slide 57

Slide 57 text

Scoped Valueへの移行可能性  Thread Local変数の目的がScoped Valuesの目的と一致する場合は、 Scoped Valuesへの移行が可能  移行が適切なケース、そうでないケースがある

Slide 58

Slide 58 text

Scoped Valueが有用なユースケース  リエントラントなコード  再帰の検出や制限が必要な場合  Nested transaction  再帰を検出すると、トランザクションの進行中に開始されたトランザクションは、すべて一番外側のトランザク ションの一部に変わる (flattened transactions)  Graphics context  プログラムのモジュール間で共有される描画コンテキスト

Slide 59

Slide 59 text

ThreadLocalが適しているユースケース  コードベースがThreadLocal変数を双方向の方法で使用している場合  コールスタックの奥にいる呼び出し元がThreadLocal.set(...)を使って遠く離れた呼び出し元にデータ を送信している  構造化されていない方法で使用している場合  純粋に変更が大変 【例】  java.text.DateFormatのインスタンスなど、生成や使用にコストがかかる オブジェクトのキャッシュ

Slide 60

Slide 60 text

Q&A

Slide 61

Slide 61 text

No content