落とし穴:ピン留めと ThreadLocal
キャリアスレッドのブロックを避けます。
「落とし穴:ピン留めと ThreadLocal」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
起こりうる問題
仮想スレッドは強力ですが、スケーラビリティを静かに損なう可能性のある2つの落とし穴があります。それはピン留めと ThreadLocal の使いすぎです。
このレッスンでは、その両方と回避方法を説明します。
ピン留めとは
ピン留めは、仮想スレッドがブロック中にキャリアからアンマウントできないときに発生します。キャリアとなる OS スレッドが動けなくなり、スケーラビリティ上の利点が失われます。
あまりにも多くのキャリアがピン留めされると、スループットが急激に低下します。
原因: synchronized 内でのブロッキング
典型的な原因は、synchronized ブロックまたはメソッドの内部でブロックすることです。モニターを保持したままブロックすると、仮想スレッドはキャリアにピン留めされたままになります。
注: JDK 24 以降(JEP 491)ではこの制限はほぼ解消されましたが、Java 21 では実際に注意が必要です。
避けるべきピン留めの例
Java 21 では、このパターンによってピン留めが発生する可能性があります。モニターを保持したままブロックしているためです。処理自体は正しく実行されますが、スケールしません。
public class Main {
static final Object lock = new Object();
static void risky() {
synchronized (lock) {
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = Thread.ofVirtual().start(Main::risky);
t.join();
System.out.println("Done (but this blocked inside synchronized)");
}
}解決策: ReentrantLock
クリティカルセクションがブロックする可能性がある場合は、synchronized を ReentrantLock に置き換えます。ロック API は仮想スレッドに適しており、アンマウントを可能にします。
import java.util.concurrent.locks.ReentrantLock;
public class Main {
static final ReentrantLock lock = new ReentrantLock();
static void safe() {
lock.lock();
try {
try { Thread.sleep(10); } catch (InterruptedException e) {}
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = Thread.ofVirtual().start(Main::safe);
t.join();
System.out.println("Done without pinning");
}
}ピン留めを診断する
次のオプションを付けて起動すると、ピン留めが発生するたびに JVM にスタックトレースを出力させることができます。
- 完全なトレースには
-Djdk.tracePinnedThreads=full - 1行のメッセージには
-Djdk.tracePinnedThreads=short
これにより、問題のある synchronized セクションを特定しやすくなります。
ThreadLocal は高コストになる
ThreadLocal はスレッドごとの状態を保存します。プラットフォームスレッドが数本であれば問題ありません。しかし、独自のコピーをそれぞれ保持する仮想スレッドが数百万あると、メモリ使用量が急増します。
ライブラリを確認してください。キャッシュ、フォーマット処理、コンテキストオブジェクトなどに ThreadLocal が隠れていることがよくあります。
ThreadLocal は引き続き使える
ThreadLocal は禁止されているわけではないので、控えめに使用してください。各仮想スレッドは、期待どおりに独自の値を持ちます。
public class Main {
static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Thread t = Thread.ofVirtual().start(() -> {
CONTEXT.set("request-42");
System.out.println("Context: " + CONTEXT.get());
CONTEXT.remove();
});
t.join();
}
}ScopedValue を優先する
Java には、変更不可データを有効期間の限定された範囲で共有するための、より軽量な代替手段として ScopedValue が導入されました。ThreadLocal のようにスレッドごとに変更可能な領域を保持するコストを避けられ、構造化並行性のモデルにも適しています。
呼び出しチェーンに読み取り専用のコンテキストを渡すだけでよい場合は、こちらを使用してください。
プールしない
もう1つの微妙な落とし穴は、仮想スレッドを希少なリソースとみなしてプールすることです。これにより、タスク間の競合や ThreadLocal の漏洩が再び発生します。
常にタスクごとに1つの仮想スレッドを作成し、終了させてください。
スケーラビリティのチェックリスト
仮想スレッドを本番環境に導入する前に、次の点を確認してください。
- ブロッキングする
synchronizedをReentrantLockに置き換える - テスト時に
jdk.tracePinnedThreadsを有効にする ThreadLocalを最小限にし、ScopedValueを優先する- 仮想スレッドを決してプールしない
理解度チェック
ブロッキングする synchronized ブロックの安全な置き換え方法を特定してください。
復習
主な落とし穴を学びました。
- ピン留め:
synchronized内でのブロッキングによってキャリアが占有される。ReentrantLockで解決する -Djdk.tracePinnedThreadsで診断する- ThreadLocal のオーバーヘッドはスレッド数に応じて増加する。
ScopedValueを優先する - 仮想スレッドを決してプールしない
これで仮想スレッドのコースは完了です。
よくある質問
「落とし穴:ピン留めと ThreadLocal」レッスンは無料ですか?
はい。「落とし穴:ピン留めと ThreadLocal」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「落とし穴:ピン留めと ThreadLocal」で何を学びますか?
キャリアスレッドのブロックを避けます。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「落とし穴:ピン留めと ThreadLocal」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 仮想スレッドとは
- 仮想スレッドの作成
- プラットフォームスレッドと仮想スレッド
- 落とし穴:ピン留めと ThreadLocal