2023.06.04 JJUG CCC セッション資料
Virtual Threads-導入の背景と効果的な使い方 -櫻庭 祐一Java in the Box
View Slide
3 分でわかる Virtual ThreadsJVM が管理する軽量スレッドFiber他言語でいうところのスループット向上が目的I/Oを多く含むシステムで効果的応答時間はちょっとだけ悪化従来のスレッドと使い方は同じ使用上の注意点はある
導入の背景Agenda歴史的経緯とともに動作原理なぜスループットが向上するのか効果的な使い方ユースケースをもとに
導入の背景歴史的経緯とともに
20042011201420231995Java 5Java 7Java 8Java 21Java 1.0
20042011201420231995Java 5Java 7Java 8Java 21Java 1.0Single Core の時代Thread = OSスレッドのラッパーThreadの切替 : スレッドのコンテキストスイッチOSThreadオブジェクト生成: 重い & メモリ消費大
OS ThreadJava ThreadJVM Stacknew Thread(new Runnable() {public void run() {foo();}}).start();run()Framevoid foo() {bar();}foo() void bar() {...}bar()
OS ThreadJava ThreadJVM Stacknew Thread(new Runnable() {public void run() {foo();}}).start();run()Framevoid foo() {bar();}foo() void bar() {...}bar()run()foo()bar()Operand StackLocal Var.
OS ThreadJava ThreadJVM Stackrun()foo()bar()Operand StackLocal Var.のコンテキストスイッチThreadのスレッドの状態OSオブジェクトの状態ThreadJVM Stack++の退避・復帰
20042011201420231995Java 5Java 7Java 8Java 21Java 1.0Single Core の時代Thread = OSスレッドのラッパーThreadの切替 : スレッドのコンテキストスイッチOSThreadオブジェクト生成: 重い & メモリ消費大重い & メモリ消費大コンテキストスイッチをなるべく発生させないスレッドスケジューリング
201120142023Java 7Java 8Java 21Java 1.019952004 Java 5Multi Core 黎明期Concurrency Utilitiesスレッドとタスクの分離スレッドプール導入Executors.newFixedThreadPool 応答時間重視Executors.newCachedThreadPool スループット重視リクエスト増大 スループット頭打ちThread per Request/Task
201120142023Java 7Java 8Java 21Java 1.019952004 Java 5Multi Core 黎明期public class ThreadPoolExecutorextends AbstractExecutorService {...private final class Workerextends AbstractQueuedSynchronizerimplements Runnable {Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}...Apache Tomcat例
20142023Java 8Java 21Java 1.019952004 Java 52011 Java 7Big Dataの時代Fork/Join Framework分割統治法 タスクを細粒度に分割して処理Work-Stealing タスクスケジューラースレッドごとにタスクキューキューが空の時は他のスレッドからタスクを盗む効率的なタスクスケジューリング
業務タスクの特徴Comp. I/O C I/O I/OC C通信、DBアクセスなど I/O処理が多いI/O処理は計算処理に対して多大な時間を要するI/Oの待ち時間が長いその中でもI/O待ちの間 CPUが遊んでしまうI/O待ちの間に他の処理を行いたい
2023 Java 21Java 1.019952004 Java 52011 Java 72014 Java 8Project Lambdaラムダ式の導入 処理を関数として記述CompletableFutureReactive Programming非同期処理を関数で記述CompletableFutureReactive Programmingex. Spring WebFluxOracle Helidon SE処理を非同期実行することでスループット向上I/Oとはいうものの従来の逐次的な記述と考え方が異なる ...例外処理が ...デバッグが ...
Java 1.019952004 Java 52011 Java 72014 Java 82023 Java 21従来の逐次的な記述で例外処理をあつかいやすくデバッグもやりやすいままでスループットを向上させたいVirtual Threads
動作原理なぜスループットが向上するのか
Virtual ThreadsPlatform Threadとタスクの仲介Platform Threadタスクからは と区別がつかないPlatform Thread にマウントして実行Carrier Threadマウントしたスレッドを と呼ぶ積極的なコンテキストスイッチ限定継続を導入してコンテキストスイッチの高速化Work-Stealingによるスレッドスケジューリング一貫性のあるスタックトレースCarrier Threadが変わってもスタックトレースが切れない
Platform Thread Platform Thread......Virtual ThreadTaskInstantiation
Platform Thread Platform Thread......Virtual ThreadTaskMount
Platform Thread Platform Thread......Virtual ThreadTaskI/O Wait
Platform Thread Platform Thread......Virtual ThreadTaskUnmount
Platform Thread Platform Thread......Virtual ThreadTask I/O Interrupt
Platform Thread Platform Thread......Virtual ThreadTaskI/O待ち時間の活用による大幅なスループット向上頻繁なコンテキストスイッチによる応答時間の悪化
Platform Thread Platform Thread......Virtual ThreadTaskpublic int read(ByteBuffer buf) throws IOException {...readLock.lock();try {...configureSocketNonBlockingIfVirtualThread();n = IOUtil.read(fd, buf, -1, nd);if (blocking) {while (IOStatus.okayToRetry(n) && isOpen()) {park(Net.POLLIN);n = IOUtil.read(fd, buf, -1, nd);}}...sun.nio.ch.SocketChannelImpl例
void park() {...boolean yielded = false;setState(PARKING);try {yielded = yieldContinuation();} finally {...}...}java.lang.VirtualThreadprivate boolean yieldContinuation() {...unmount();try {return Continuation.yield(VTHREAD_SCOPE);} finally {mount();...}}限定継続
効果的な使い方ユースケースをもとに
Virtual Threadsの使い方ExecutorServiceを介して利用するExecutors.newVirtualThreadPerTaskExecutor()Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory())orVirtual Threadの直接生成も可能だが、基本はやらないExecutorServiceを定義する場合だけ独自のExecutorServiceはJava 19からAutoClosable
Virtual Threadsの使い方try (var pool= Executors.newVirtualThreadPerTaskExecutor()) {Runnable task = ...;pool.submit(task);}
VT 使いたいYFWを使ってる ?VT使う必要なしWebFluxなどのリアクティブ系or CompletableFutureゴリゴリ書けるSpring Boot, Jakarta EE などFWの対応待ちその他 使ってないor
VT使う必要なしor CompletableFutureゴリゴリ書けるSpring Boot, Jakarta EE などFWの対応待ちその他 使ってないorボトルネックは?データ量 / 計算量 ParallelStreamFork/Join FWVector APIFileMemory MapDB通信 /VirtualThread
スレッドの注意点 Virtual Threads特にsynchronizedをなるべく使わないようにする1.synchronized = モニタロックリソースへのアクセスを 1 つのスレッドだけに限定他のスレッドをブロックするボトルネックになりやすいVirtual Threadでは Carrier Threadをブロックしてしまう
スレッドの注意点 Virtual Threads特にsynchronized を使わないようにするにはイミュータブルクラスの活用record 型の利用状態が変化しなければ複数スレッドからもアクセス可処理結果の受け渡しがある場合 Callableを使うどうしてもロックが必要であればsynchronizedではなくReentrantLock クラスを使うjava.util.concurrent.locksにロッククラスあり他にも
スレッドの注意点 Virtual Threads特にThreadLocal を使わない2.ThreadLocal はミュータブル & ライフタイムが不明確Virtual Threads ThreadLocalをサポートしているがはスケールしないためボトルネック化ScopedValueで代用するJava 21 Preview JEPではただしFWが ThreadLocal を使っている場合対応してくれるまで待ちましょう
スレッドの注意点 Virtual Threads特にスレッドを使いまわさない3.Virtual Threadsは使い捨てExecutorServiceスレッドのプールは にまかせるタスクを記述することに集中しようクラスの4. Thread APIをもう 1 度チェック@Deprecated(forRemoval=true)なメソッドstop, suspend, resumeなど
ConclusionVirtual Threadsによりスループットが向上I/Oの待ち時間を有効活用Virtual Threadsは従来のスレッドを同じ使い方使用時の注意点を把握するFWで Virtual Threads がサポートされるのを楽しみに待つ