Solaris™ プラットフォームでのスレッド優先順位

このドキュメントでは、Java™ 仮想マシン (JVM) が、JVM (Java スレッド) で実行中のスレッドの優先順位を、Solaris のネイティブスレッドの優先順位へマッピングする方法について説明します。Solaris スレッドと JVM の現在と過去の両方の実装について説明します。

内容説明: Java スレッド

JVM では以下を含め、Java スレッドの 10 の論理的優先順位の範囲を定義します。

java.lang.Thread.MIN_PRIORITY  = 1
java.lang.Thread.NORM_PRIORITY = 5
java.lang.Thread.MAX_PRIORITY  = 10

これら [1..10] の値は Thread.setPriority(int) に渡され、Java スレッドに優先順位が割り当てられます。Java スレッドのデフォルトの優先順位は NORM_PRIORITY です(setPriority を明示的に呼び出さない Java スレッドは、NORM_PRIORITY で動作する)。JVM では、この値を無視するなど、選択する任意の方法で優先順位を実装できます。

Java HotSpot™ 仮想マシンは現在、各 Java スレッドを一意のネイティブスレッドに関連付けます。Java スレッドとネイティブスレッドの関係は安定しており、Java スレッドの生存期間の間、持続します。

内容説明: Solaris

Libthread の形式: T1 および T2

Solaris 9 より前では、デフォルトの libthread (スレッドライブラリ) は T1 libthread と呼ばれるものでした。T1 では M:N スレッドモデルを提供し、M 個のネイティブスレッド が N 個のカーネルスレッド (LWP) 上で多重化されました。ネイティブのスレッドと LWP との関係は流動的かつ動的であり、スレッドを実行している状態でも、またスレッドの関知なく変更されました。Solaris では、LWP のディスパッチ優先順位を変更するための priocntl() システムコールを提供しましたが、LWP とネイティブスレッドの関係が不安定だったため、ネイティブスレッドのディスパッチ優先順位を変更する確実な方法がありませんでした。(JVM では、Java スレッドを実行している LWP の優先順位を変更できましたが、JVM の関知なくスレッドが別の LWP に割り当てられることがありました。)

Solaris 9 以降のデフォルトの libthread である T2 では、より簡単で強力な 1:1 スレッドモデルを実現しています。各ネイティブスレッドは、一意のスレッドの LWP に割り当てられ、その関係はネイティブスレッドの生存期間中、安定しています。

T1 および T2 は、アプリケーションがスレッドのプロセスにローカルな優先順位の設定に使用する thr_setprio() API を公開しています。thr_setprio() から割り当てられる値は、プロセスローカル属性で、カーネルスケジューラには表示されません。thr_setprio() 優先順位は、競合するプロセスローカルの相互排他ロックに関連するスレッドのキューなど、ユーザレベルのプロセスローカルのスリープキューでスレッドの配置と順序付けを制御します。HotSpot では、ほとんどの相互排他ロックは競合しておらず、条件変数には通常、0 か 1 のスレッドがあります。したがって、thr_setprio() 優先順位による順序付けは、ほとんどの Java スレッドには影響を及ぼしません。thr_setprio() 関数は、0 から 127 までの優先順位の値をサポートし、127 は最高の優先順位を表します。

T1 もスレッドの優先順位を使用して、基本的なユーザモードの優先使用を実現します。T1 では、ローカルの実行可能キューの全スレッドの thr_setprio() 優先順位は、現在実行中の LWP に関連付けられたすべての非結合スレッドの優先順位以下でなければならないという不変条件を維持します。この不変条件が危うくなる場合、T1 は、一番低い優先度で実行中のスレッドを横取りして、不変条件を確立するためにその LWP を「流用」します。

優先使用されるのは以下のような場合です。

T2 の初期のバージョンは、代替スレッドライブラリと呼ばれ、Solaris 8 で導入されました。

T1、T2、および LWP の詳細は、以下のサイトを参照してください。

ネイティブ LWP の優先順位

Solaris の LWP の優先順位は、ある 1 つのスレッドが他のスレッドと比較して受け取る CPU サイクル数に影響を及ぼします。Solaris スケジューラは、様々な要素から優先順位を使用して、あるスレッドを他のスレッドより優先使用する必要があるかどうかを決定し、スレッドを実行する頻度、およびスレッドの実行時間を決定します。ネイティブの LWP 優先順位は、priocntl() システムコールにより割り当てられます。

まとめ

要約すると、Thread.setPriority メソッドにより設定された優先順位を持つ Java スレッドがあります。Java スレッドはネイティブスレッドで動作します。thr_setprio() 関数は、ネイティブスレッドの優先順位を変更するために使用されます。ネイティブスレッドは LWP で動作します。priocntl() システムコールは LWP の優先順位を変更するために使用されます。

スレッド優先順位の実装の経緯

1.4.2 より前

1.4.2 より前のバージョンでは、Java スレッドが Thread.setPriority メソッドを呼び出した場合、またはスレッドが作成された場合、HotSpot が thr_setprio() を呼び出して、Java の優先順位をネイティブの優先順位にマッピングしていました。thr_setprio() を呼び出しても、Java スレッドの実行時の動作にはほとんど影響がありませんでした。JVM は配下の LWP の優先順位を調整するために priocntl() を呼び出しませんでした。1.4.2 の開発期間中は、Solaris で唯一使用可能な libthread は以前の T1 の libthread だけであったため、このような設計上の選択が意識的になされました。


注: JVM では、スレッドの作成時に、THR_BOUND を指定して、ネイティブスレッドを T1 下の LWP に 1:1 でバインドさせることができました。ただし、JVM に接続されたスレッドが THR_BOUND でない可能性があり、最初のスレッドが THR_BOUND ではないため、THR_BOUND は十分とはいえません。HotSpot の実装者は、Solaris にはスレッドの作成後にバインドさせる方法がないことを考慮し、Java スレッドが setPriority() を呼び出した場合に LWP の優先順位を変更しないほうが賢明だと判断しました。

1.4.2

1.4.2 では、T1 または T2 下で実行しているかどうかを HotSpot が起動時に判断できました。JVM が T1 下で起動した場合、優先順位の効果は前のリリースとまったく同じになります。

T2 では、Thread.setPriority メソッドへの呼び出しを、thr_setprio() (ネイティブのプロセスローカルの優先順位を変更するため) と priocntl() (配下の LWP のディスパッチ優先順位を変更するため) の両方の呼び出しに翻訳しました。JVM は、TS (時分割)、IA (対話式)、および RT (リアルタイム) スケジューリングクラスで実行中のスレッドに対してのみ、priocntl() を呼び出します。スケジューリングクラスの詳細については、Solaris priocntl(2) のマニュアルページを参照してください。Java スレッドが TS、IA、または、 RT スケジューリングクラスにない場合、JVM は priocntl() で配下の LWP の優先順位を設定しません。

残念なことに、TS および IA のスケジューリングクラスのネイティブスレッドのデフォルトの優先順位は、可能な範囲で最高の優先順位です。Java スレッドのデフォルトの論理的優先順位は NORM_PRIORITY で、これは、Java スレッド優先順位の領域の中間です。JVM が NORM_PRIORITY をネイティブと LWP の優先順位にマッピングすると、その結果、値がデフォルトのネイティブ優先順位よりも低くなります。JVM を実行している 2 つの CPU システムがあり、JVM に 2 つの Java スレッドがあり、その両方が NORM_PRIORITY にあるとします。よくあるように、スレッドが IA または TS スケジューリングクラスにあると想定します。Java スレッドが作成されると、JVM は priocntl() を呼び出して、NORM_PRIORITY を TS または IA 優先順位バンドの中間にマッピングします。さらに、他のプロセスで 2 つのネイティブの「C」スレッドが Java スレッドと同時に実行中/実行可能だと想定します。ネイティブの C スレッドと Java スレッドは両方とも CPU に依存してスピンし、演算を行います。ネイティブスレッドは、TS および IA スケジューリングクラス内で最高の優先順位で実行され、JVM スレッドは中間の優先順位で実行されます。4 つのスレッドが CPU サイクルで競合しているため、ネイティブスレッドが比較的多くの CPU サイクルを受け取り、ある意味では Java スレッドが不利になります。こうした状況は、Java スレッドが通常のスレッドと競合して、システムが飽和状態のときに発生します。

逆の面からいえば、低い相対的な優先度を使用する利点は、スレッドが低い優先順位で実行されている TS および IA スケジューリングクラスが長いクォンタムを受けるため、実行可能になった優先度の高いスレッドにより中間のクォンタムが優先使用されないことです。クォンタムが長いと、優先使用からのコンテキストの切換率が下がるため、多くの場合、サーバアプリケーションのスレッドが有利になります。スレッドは、CPU で長い間動作することが許されるため、一時的にキャッシュの再ロードに時間がかかっても、長いクォンタムの間に償却されます (スレッドが CPU にスケジュールされた直後の期間。スレッドが CPU のデータキャッシュを再生成して既存のスレッドデータを置き換えるため、スレッドのキャッシュ誤り率が高くなる場合)。

JRE 5.0

JRE 5.0 では、1.4.2 と同じ優先順位のマッピングを提供します。ただし、[10...5] の範囲での Java 優先順位はすべて可能な範囲で最高の優先順位の TS または IA の優先順位にマッピングされる点が異なります。[1..4] の範囲の優先順位は対応する低いネイティブの TS または IA の優先順位にマッピングされます。この変更による利点は、NORM_PRIORITY の Java スレッドが期待どおりネイティブスレッドと競合するようになったことです。よくあることですが、Java スレッドとネイティブスレッドのどちらも明示的に優先順位を設定しない場合、両方のスレッドのクラスが同じ占有スペースで競合し、TS または IA スケジューリングクラスのもっとも高い優先順位で実行されます。

Java スレッドが setPriority() を使用して明示的にその優先順位を設定しない場合、1.4.2 より前に使用されていた Java スレッドの効果的な LWP 優先順位の動作が復元されます。この実装の短所は、5 から 10 の Java の優先順位が識別されないことです。たとえば、論理的優先順位 8 の Java スレッドは、優先順位 9 の Java スレッド と同じ LWP 優先順位にマッピングされます。

その他

次の内容は、HotSpot のすべてのバージョンに当てはまります。

一般的なスケジューリング問題: 優先順位、移譲、およびモニターの公平性

Thread.setPriority および Thread.yield メソッドは助言的なものです。この 2 つのメソッドはアプリケーションから JVM へのヒントを構成します。堅牢で正しく記述されたプラットフォームに依存しないコードは、setPriority() および yield() を使用して、アプリケーションのパフォーマンスを最適化しますが、正確さについてはそれらの属性に依存するべきではありません。同様に、スレッドにモニターの所有権が付与される順序、または notifynotifyAll メソッドに対してスレッドを呼び出す順序についても依存するべきではありません。この問題についての詳しい説明は、Joshua Bloch の著書『Effective Java Programming Language Guide』の第 9 章、「Threads」を参照してください。