Javaでのyield()
メソッドの使用、特に以下のコード例では、少し混乱しています。また、私はyield()が「スレッドの実行を防ぐために使用される」ことを読んだことがあります。
私の質問は:
以下のコードは、yield()
を使用する場合と使用しない場合の両方で同じ出力になると考えています。これは正しいです?
実際、yield()
の主な用途は何ですか?
yield()
はjoin()
およびinterrupt()
メソッドとどのように異なりますか?
コード例:
public class MyRunnable implements Runnable {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
for(int i=0; i<5; i++) {
System.out.println("Inside main");
}
}
public void run() {
for(int i=0; i<5; i++) {
System.out.println("Inside run");
Thread.yield();
}
}
}
上記のコードを使用して、yield()
を使用する場合と使用しない場合の両方で同じ出力を取得します。
Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run
質問が報奨金で再有効化され、yield
の実際の使用法を尋ねています。私の経験から例を挙げましょう。
知っているように、yield
は、呼び出しスレッドに、実行中のプロセッサを強制的に放棄させ、別のスレッドの実行をスケジュールできるようにします。これは、現在のスレッドが現在作業を終了しているが、すぐにキューの先頭に戻り、何らかの条件が変更されたかどうかを確認する場合に役立ちます。これは条件変数とどう違うのですか? yield
を使用すると、スレッドは実行状態にはるかに速く戻ることができます。条件変数で待機する場合、スレッドは中断され、別のスレッドが継続する必要があることを通知するまで待機する必要があります。 yield
は基本的に「別のスレッドの実行を許可しますが、状態が非常に速く変化することが予想されるため、すぐに作業に戻ることができます」と言います。これは、状態が急速に変化する可能性がありますが、スレッドを一時停止するとパフォーマンスが大幅に低下する、ビジースピンを示唆しています。
しかし、十分なせせらぎ、具体例は次のとおりです。波面平行パターン。この問題の基本的な例は、0と1で満たされた2次元配列の1の個々の「島」を計算することです。 「島」は、垂直方向または水平方向に互いに隣接するセルのグループです。
1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 1 1
ここには、左上と右下の2つの1の島があります。
簡単な解決策は、配列全体に最初のパスを作成し、1の値を増分カウンターで置き換えて、各1が行の主要な順序でシーケンス番号に置き換えられるようにすることです。
1 0 0 0
2 3 0 0
0 0 0 4
0 0 5 6
0 0 7 8
次のステップで、各値は、それ自体とその近隣の値の間の最小値に置き換えられます。
1 0 0 0
1 1 0 0
0 0 0 4
0 0 4 4
0 0 4 4
これで、2つの島があることを簡単に判断できます。
並行して実行したい部分は、最小値を計算するステップです。あまり詳細に説明することなく、各スレッドはインターリーブ方式で行を取得し、上の行を処理するスレッドによって計算された値に依存します。したがって、各スレッドは、前の行を処理するスレッドよりもわずかに遅れる必要がありますが、妥当な時間内に追いつく必要もあります。詳細と実装については、自分で このドキュメント で説明しています。 sleep(0)
の使用に注意してください。これはyield
とほぼ同等のCです。
この場合、各スレッドを強制的に一時停止するためにyield
が使用されましたが、その間、隣接する行を処理するスレッドは非常に速く進むため、条件変数は悲惨な選択になります。
ご覧のとおり、yield
は非常に細かい最適化です。間違った場所で使用するめったに変更されない状態で待機すると、CPUが過剰に使用されます。
長いおしゃべりでごめんなさい、私が自分自身を明確にしたことを願っています。
yield()
、interrupt()
およびjoin()
の違いについて-一般に、Javaだけでなく:
Javaについては、特に
接合:
Thread.joinの使用方法 (ここではStackOverflow)
降伏:
中断:
Thread.interrupt()evil? (ここではStackOverflowにあります)
まず、実際の説明は
現在実行中のスレッドオブジェクトを一時的に一時停止し、他のスレッドの実行を許可します。
現在、新しいスレッドのrun
メソッドが実行される前にメインスレッドがループを5回実行する可能性が非常に高いため、yield
へのすべての呼び出しは、メインスレッドが実行されます。
join
は、join()
で呼び出されているスレッドの実行が完了するまで、現在のスレッドを停止します。
interrupt
は、呼び出されているスレッドに割り込み、 InterruptedException を引き起こします。
yield
は、他のスレッドへのコンテキスト切り替えを許可するため、このスレッドはプロセスのCPU使用率全体を消費しません。
現在の回答は古くなっており、最近の変更を考慮すると改訂が必要です。
6〜9以降、Javaバージョン間で[実用Thread.yield()
の違いはありません。
TL; DR;
OpenJDKソースコードに基づく結論( http://hg.openjdk.Java.net/ )。
USDTプローブ(システムトレース情報については dtraceガイド で説明されています)およびJVMプロパティConvertYieldToSleep
のHotSpotサポートを考慮しない場合、yield()
のソースコードはほとんど同じです。以下の説明を参照してください。
Java 9:
Thread.yield()
はOS固有のメソッドos::naked_yield()
を呼び出します:
Linuxの場合:
void os::naked_yield() {
sched_yield();
}
Windowsの場合:
void os::naked_yield() {
SwitchToThread();
}
Java 8以前:
Thread.yield()
はOS固有のメソッドos::yield()
を呼び出します:
Linuxの場合:
void os::yield() {
sched_yield();
}
Windowsの場合:
void os::yield() { os::NakedYield(); }
ご覧のとおり、Linux上のThread.yeald()
は、すべてのJavaバージョンで同一です。
JDK 8のWindowsのos::NakedYield()
を見てみましょう。
os::YieldResult os::NakedYield() {
// Use either SwitchToThread() or Sleep(0)
// Consider passing back the return value from SwitchToThread().
if (os::Kernel32Dll::SwitchToThreadAvailable()) {
return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
} else {
Sleep(0);
}
return os::YIELD_UNKNOWN ;
}
Win32 APIのSwitchToThread()
メソッドの存在の追加チェックにおけるJava 9とJava 8の違い。 Java 6にも同じコードが存在します。
JDK 7のos::NakedYield()
のソースコードは若干異なりますが、動作は同じです。
os::YieldResult os::NakedYield() {
// Use either SwitchToThread() or Sleep(0)
// Consider passing back the return value from SwitchToThread().
// We use GetProcAddress() as ancient Win9X versions of windows doen't support SwitchToThread.
// In that case we revert to Sleep(0).
static volatile STTSignature stt = (STTSignature) 1 ;
if (stt == ((STTSignature) 1)) {
stt = (STTSignature) ::GetProcAddress (LoadLibrary ("Kernel32.dll"), "SwitchToThread") ;
// It's OK if threads race during initialization as the operation above is idempotent.
}
if (stt != NULL) {
return (*stt)() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
} else {
Sleep (0) ;
}
return os::YIELD_UNKNOWN ;
}
Windows XPおよびWindows Server 2003からSwitchToThread()
メソッドが使用可能になったため、追加のチェックは削除されました( msdn notes を参照)。
実際、yield()の主な用途は何ですか?
Yieldは、現在のスレッドを停止し、より高い優先度でスレッドの実行を開始できることをCPUに提案します。つまり、現在のスレッドに低い優先度の値を割り当てて、より重要なスレッドのためのスペースを確保します。
以下のコードは、yield()を使用する場合と使用しない場合の両方で同じ出力になると思います。これは正しいです?
いいえ、2つは異なる結果を生成します。 yield()がなければ、スレッドが制御を取得すると、「Inside run」ループを一度に実行します。ただし、yield()を使用すると、スレッドが制御を取得すると、「Inside run」を1回出力してから、他のスレッドに制御を渡します(存在する場合)。保留中のスレッドがない場合、このスレッドは再び再開されます。そのため、「内部実行」が実行されるたびに、実行する他のスレッドを探し、使用可能なスレッドがない場合、現在のスレッドは実行を続けます。
Yield()はjoin()およびinterrupt()メソッドとどのように違いますか?
yield()は他の重要なスレッドに余裕を与えるため、join()は別のスレッドが実行を完了するのを待つため、interrupt()は現在実行中のスレッドを中断して他の何かをするためです。
Thread.yield()
により、スレッドは「実行中」状態から「実行可能」状態になります。注:スレッドが「待機中」状態になることはありません。