Java の仮想スレッドは、従来の OS スレッドに代わる軽量のスレッドを提供し、効率的な同時実行管理を可能にします。しかし、最適なパフォーマンスを得るには、彼らの行動を理解することが重要です。このブログ投稿では、仮想スレッドの実行に影響を与える可能性のあるシナリオであるピン留めについて詳しく説明し、それを監視して対処するためのテクニックを探ります。
Java の仮想スレッドは、基礎となるオペレーティング システム スレッド (キャリア スレッド) 上で実行される管理対象エンティティです。これらは、多数の OS スレッドを作成する場合と比べて、オーバーヘッドが低いため、同時実行を処理する効率的な方法を提供します。 JVM は仮想スレッドをキャリア スレッドに動的にマッピングするため、リソースの使用率が向上します。
JVM によって管理: オペレーティング システムによって直接管理される OS スレッドとは異なり、仮想スレッドは Java 仮想マシン (JVM) によって作成およびスケジュールされます。これにより、JVM 環境内でのよりきめの細かい制御と最適化が可能になります。
オーバーヘッドの削減: 仮想スレッドの作成と管理で発生するオーバーヘッドは、OS スレッドに比べて大幅に低くなります。これは、JVM が少数の基盤となる OS スレッドを利用して、より大きな仮想スレッドのプールを効率的に管理できるためです。
既存のコードとの互換性: 仮想スレッドは、既存の Java コードとシームレスに統合されるように設計されています。これらは従来の OS スレッドと並行して使用でき、Executor や ExecutorService などの使い慣れた構造内で動作して同時実行を管理します。
次の図は、仮想スレッドとプラットフォーム スレッドの関係を示しています。
ピン留めは、仮想スレッドがそのキャリア スレッドに結び付けられると発生します。これは本質的に、仮想スレッドが固定状態にある間はプリエンプト (別のキャリア スレッドに切り替える) できないことを意味します。固定をトリガーする一般的なシナリオは次のとおりです:
コード例:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) throws InterruptedException { final Counter counter = new Counter(); Runnable task = () -> { for (int i = 0; iこの例では、仮想スレッドが同期ブロックに入ると、そのキャリア スレッドに固定されますが、これは常に当てはまるわけではありません。 Java の synchronized キーワードだけでは、仮想スレッドでスレッド固定を引き起こすのに十分ではありません。スレッドの固定が発生するには、仮想スレッドがパークをトリガーし、最終的にキャリア スレッドからのアンマウントを禁止するブロック ポイントが同期ブロック内に存在する必要があります。スレッドの固定は、軽量/仮想スレッドを使用する利点を無効にするため、パフォーマンスの低下を引き起こす可能性があります。
仮想スレッドがブロッキング ポイントに遭遇すると、その状態はパーキングに移行します。この状態遷移は、VirtualThread.park() メソッドを呼び出すことで示されます:
// JDK core code void park() { assert Thread.currentThread() == this; // complete immediately if parking permit available or interrupted if (getAndSetParkPermit(false) || interrupted) return; // park the thread setState(PARKING); try { if (!yieldContinuation()) { // park on the carrier thread when pinned parkOnCarrierThread(false, 0); } } finally { assert (Thread.currentThread() == this) && (state() == RUNNING); } }この概念を説明するコード サンプルを見てみましょう:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { Counter counter = new Counter(); Runnable task = () -> { for (int i = 0; i
-Djdk.tracePinnedThreads=full フラグは、仮想スレッドの固定に関する詳細なトレース情報を提供する JVM 起動引数です。有効にすると、
のようなイベントがログに記録されます。パフォーマンスのオーバーヘッドが発生するため、このフラグはデバッグ セッション中にのみ慎重に使用してください。
デモ コードをコンパイルします:
javac Main.java
コンパイルされたコードを -Djdk.tracePinnedThreads=full フラグで開始します:
java -Djdk.tracePinnedThreads=full Main
コンソールの出力を確認します。仮想スレッドの固定に関する詳細情報が表示されます。
Thread[#29,ForkJoinPool-1-worker-1,5,CarrierThreads] java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:183) java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393) java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:621) java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:791) java.base/java.lang.Thread.sleep(Thread.java:507) Counter.increment(Main.java:38)
ピン留めは、仮想スレッドのパフォーマンスを妨げる望ましくないシナリオです。リエントラント ロックは、ピンニングを防ぐ効果的なツールとして機能します。リエントラント ロックを使用してピン留めの状況を軽減する方法は次のとおりです:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.ReentrantLock; public class Main { public static void main(String[] args) { Counter counter = new Counter(); Runnable task = () -> { for (int i = 0; i更新された例では、同期ブロックの代わりに ReentrantLock を使用します。スレッドはロックを取得し、操作の完了後すぐに解放できるため、ロックを長期間保持する可能性がある同期ブロックと比較して、固定期間を短縮できる可能性があります。
結論は
Java の仮想スレッドは、言語の進化と機能の証拠となります。これらは、従来の OS スレッドに代わる新鮮で軽量な代替手段を提供し、効率的な同時実行管理への架け橋となります。時間をかけてスレッドの固定などの重要な概念を深く掘り下げて理解することで、開発者はこれらの軽量スレッドの可能性を最大限に活用するためのノウハウを得ることができます。この知識は、開発者が今後の機能を活用するための準備を整えるだけでなく、現在のプロジェクトで複雑な同時実行制御の問題をより効果的に解決できるようにするためにも役立ちます。
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3