CodeGym /Java Blog /ランダム /スレッドの管理。volatile キーワードと yield() メソッド
John Squirrels
レベル 41
San Francisco

スレッドの管理。volatile キーワードと yield() メソッド

ランダム グループに公開済み
やあ!私たちはマルチスレッドの研究を続けます。volatile今回はそのキーワードとその手法について見ていきましょうyield()。飛び込んでみましょう:)

不安定なキーワード

マルチスレッド アプリケーションを作成する場合、2 つの重大な問題に遭遇する可能性があります。 まず、マルチスレッド アプリケーションが実行されている場合、さまざまなスレッドが変数の値をキャッシュできます(これについては、「volatile の使用」というタイトルのレッスンですでに説明しました)。1 つのスレッドが変数の値を変更しても、2 番目のスレッドは変数のキャッシュされたコピーを操作しているため、その変更が認識されないという状況が発生することがあります。 当然のことながら、その結果は深刻になる可能性があります。それが単なる古い変数ではなく、銀行口座残高が突然ランダムに上下し始めたと仮定してください:) それは楽しいことではありませんよね? 2 番目に、Java では、すべてのプリミティブ型の読み取りおよび書き込み操作が行われます。longdouble、アトミックです。 たとえば、intあるスレッドで変数の値を変更し、別のスレッドでその変数の値を読み取る場合、古い値か新しい値、つまり変更の結果得られた値のいずれかを取得します。スレッド 1 には「中間値」はありません。longただし、これはs とsでは機能しませんdouble。なぜ? クロスプラットフォームのサポートのため。 初級レベルで、Java の基本原則は「一度書けばどこでも実行できる」であると述べたことを覚えていますか? つまり、クロスプラットフォームのサポートを意味します。言い換えれば、Java アプリケーションはあらゆる種類の異なるプラットフォーム上で実行されます。たとえば、Windows オペレーティング システム、Linux または MacOS のさまざまなバージョン。どれも問題なく動作します。64ビットで重み付けすると、longdoubleJava で「最も重い」プリミティブです。また、特定の 32 ビット プラットフォームでは、64 ビット変数のアトミックな読み取りと書き込みが実装されていません。このような変数は 2 回の操作で読み書きされます。まず、最初の 32 ビットが変数に書き込まれ、次に別の 32 ビットが書き込まれます。その結果、問題が発生する可能性があります。1 つのスレッドが 64 ビット値をX変数に書き込み、これを 2 つの操作で実行します。同時に、2 番目のスレッドは変数の値を読み取ろうとし、これら 2 つの操作の間、つまり最初の 32 ビットが書き込まれているが、2 番目の 32 ビットがまだ書き込まれていないときに読み取りを実行します。その結果、中間の誤った値が読み取られ、バグが発生します。たとえば、そのようなプラットフォーム上で9223372036854775809に番号を書き込もうとするとします。 変数に変換すると、64 ビットを占有します。バイナリ形式では、次のようになります。 10000000000000000000000000000000000000000000000000000000000000001 最初のスレッドは、変数への数値の書き込みを開始します。まず最初の 32 ビット (1000000000000000000000000000000)を書き込み 、次に 2 番目の 32 ビット (0000000000000000000000000000001) を書き込みます。 そして、2 番目のスレッドがこれらの操作の間に挟まれて、すでに書き込まれている最初の 32 ビットである変数の中間値 (10000000000000000000000000000000) を読み取ることができます。10 進法では、この数値は 2,147,483,648 です。言い換えれば、数値 9223372036854775809 を変数に書き込みたかっただけですが、一部のプラットフォームではこの操作がアトミックではないため、どこからともなく現れて未知の影響を与える邪悪な数値 2,147,483,648 が作成されてしまいます。プログラム。2 番目のスレッドは、変数の書き込みが完了する前に変数の値を読み取るだけです。つまり、スレッドは最初の 32 ビットを参照しますが、2 番目の 32 ビットは参照しません。もちろん、これらの問題は昨日発生したわけではありません。Java は、単一のキーワードでこれらを解決しますvolatile。を使用すると、volatileプログラム内で変数を宣言するときのキーワード…

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…だということだ:
  1. 常にアトミックに読み書きされます。64 ビットdoubleまたはlong.
  2. Java マシンはそれをキャッシュしません。したがって、10 個のスレッドが独自のローカル コピーを使用して動作するような状況は発生しません。
したがって、2 つの非常に深刻な問題がたった 1 つの単語で解決さ​​れます :)

yield() メソッド

クラスのメソッドの多くはすでに確認しましたThreadが、初めて知る重要なメソッドがあります。それはyield()方法です。そして、それはまさにその名前が示すとおりのことを行います。 スレッド上でメソッドをスレッドの管理。 volatile キーワードと yield() メソッド - 2呼び出すと、実際には他のスレッドと通信します。yield特に急いでどこかに行くつもりはないので、プロセッサー時間を確保することが重要な場合は、待ってください。」これがどのように機能するかを示す簡単な例を次に示します。

public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
3 つのスレッド、Thread-0Thread-1、および を順番に作成して開始しますThread-2Thread-0最初に始めてすぐに他の人に譲ります。それからThread-1起動してまた譲ります。それからThread-2開始され、これも結果をもたらします。これ以上スレッドはなく、Thread-2最後にその場所を譲った後、スレッド スケジューラは「うーん、これ以上新しいスレッドはありません」と言います。誰が列に並んでいますか? 以前にその地位を譲ったのは誰Thread-2ですか?だったそうですThread-1。わかりました、それは実行させてみましょうという意味です。 Thread-1作業が完了すると、スレッド スケジューラは調整を続けます。「わかりました、Thread-1完了しました。」他に列に並んでいる人はいますか?』Thread-0 はキュー内にあります。直前にその場所を譲りました。Thread-1。今度は順番が来て、最後まで実行されます。次に、スケジューラはスレッドの調整を終了します。「わかりました、Thread-2他のスレッドに譲歩しました。これですべてが完了しました。」最後に譲歩したのはあなただったので、今度はあなたの番です。」その後、Thread-2完了まで実行されます。 コンソール出力は次のようになります。 Thread-0 がその場所を他の人に譲ります Thread-1 がその場所を他の人に譲ります Thread-2 がその場所を他の人に譲ります Thread-1 は実行を終了しました。スレッド 0 の実行が終了しました。スレッド 2 の実行が終了しました。 もちろん、スレッド スケジューラは別の順序 (たとえば、0-1-2 ではなく 2-1-0) でスレッドを開始する可能性がありますが、原則は同じです。

前に発生するルール

今日最後に触れることは、「前に起こる」という概念です。すでにご存知のとおり、Java では、スレッド スケジューラが、タスクを実行するためにスレッドに時間とリソースを割り当てる際の作業の大部分を実行します。また、スレッドが通常は予測不可能なランダムな順序で実行される様子も繰り返し見てきました。そして一般に、以前に行った「シーケンシャル」プログラミングの後では、マルチスレッド プログラミングはランダムなもののように見えます。あなたはすでに、さまざまなメソッドを使用してマルチスレッド プログラムのフローを制御できると考えるようになりました。しかし、Java のマルチスレッドにはもう 1 つの柱があります。それは 4 つの「happens before」ルールです。これらのルールを理解するのは非常に簡単です。2 つのスレッドがあると想像してくださいAB。これらの各スレッドは、操作1とを実行できます2各ルールで「 A happens-before B 」と言うときは、A操作前にスレッドによって行われたすべての変更1と、この操作から生じた変更が、B操作の2実行時およびその後にスレッドに表示されることを意味します。各ルールは、マルチスレッド プログラムを作成するときに、特定のイベントが 100% の確率で他のイベントよりも先に発生すること、および操作時にスレッドが操作中に行った変更を常に認識することを2保証しBます。それらを見直してみましょう。 A1

ルール1。

ミューテックスの解放は、同じモニターが別のスレッドによって取得される前に行われます。ここですべてを理解していると思います。オブジェクトまたはクラスのミューテックスが 1 つのスレッド、たとえば thread によって取得される場合、A別のスレッド ( thread B) はそれを同時に取得することはできません。ミューテックスが解放されるまで待つ必要があります。

ルール2。

このThread.start()メソッドはの前に発生します Thread.run()。繰り返しますが、ここでは難しいことは何もありません。メソッド内でコードの実行を開始するにはrun()start()スレッド上でメソッドを呼び出す必要があることはすでにご存知でしょう。具体的には、メソッド自体ではなく、開始メソッドですrun()。このルールにより、 が開始されると、呼び出される前に設定されたすべての変数の値がメソッドThread.start()内で表示されるようになりますrun()

ルール3。

run()メソッドの終了は、メソッドから戻る前に発生しますjoin()Aとの 2 つのスレッドに戻りましょうB。このメソッドを呼び出して、join()スレッドが作業を実行する前にBスレッドの完了を待機することが保証されるようにします。Aこれは、A オブジェクトのrun()メソッドが最後まで実行されることが保証されていることを意味します。また、run()スレッドのメソッド内で発生するデータへのすべての変更は、スレッドが作業を終了して独自の作業を開始できるように なるまで待機した後、Aスレッド内で表示されることが 100% 保証されます。BA

ルール4。

volatile変数への書き込みは、同じ変数からの読み取りの前に行われます。キーワードを使用するとvolatile、実際には常に現在の値が取得されます。または が付いている場合でもlong(doubleここで発生する可能性のある問題については前に説明しました)。 すでに理解されているように、一部のスレッドで行われた変更は、他のスレッドに常に表示されるわけではありません。しかし、もちろん、そのような行動が私たちに適さない状況は非常に頻繁にあります。スレッド上の変数に値を代入するとしますA

int z;

….

z = 555;
Bスレッドがコンソールに変数の値を表示する必要が ある場合z、割り当てられた値がわからないため、簡単に 0 が表示される可能性があります。zしかし、ルール 4 は、変数を として宣言した場合volatile、あるスレッドでのその値への変更が常に別のスレッドで可視になることを保証します。volatile先ほどのコードに 単語を追加すると…

volatile int z;

….

z = 555;
B...これで、スレッドが0 を表示する可能性の ある状況を回避できます。volatile変数への書き込みは、変数からの読み取りの前に行われます。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION