こんにちは、Habr! 「The Java Language Specification( Chapter 17.スレッドとロック )」というオリジナルの記事の翻訳を紹介します。第17章スレッドとロック前の章のほとんどの議論は、同時に実行されるコードの動作と、単一のステートメントまたは式として同時に実行されるコード、つまり 1つのスレッドで、JVM(Java仮想マシン)は複数のスレッドを同時にサポートできます。 これらのスレッドは、共有メインメモリにある値とオブジェクトに作用するコードを独立して使用します。 スレッドは、複数のハードウェアプロセッサの使用、単一のハードウェアプロセッサの一時的な分割、または複数のハードウェアプロセッサの一時的な分割によってサポートできます。
スレッドは
Threadクラスで表されます。 ユーザーがスレッドを作成できる唯一の方法は、このクラスのオブジェクトを作成することです。 各スレッドは何らかのオブジェクトに関連付けられています。 対応する
Threadオブジェクトで
start()メソッドが呼び出されると、
スレッドは実行を開始します。
特に同期が正しく実行されない場合のスレッドの動作は理解できない場合があり、期待に合わない場合があります。 この章では、マルチスレッドプログラミングのセマンティクスについて説明します。 多くのスレッドによって更新される共有メモリの読み取りに使用できる値に応じたルールが含まれています。 仕様はさまざまなアーキテクチャの
メモリモデルに似ているため、このセマンティクスはJavaプログラミング言語の
メモリモデルとして知られています。 混乱がない場合は、これらのルールを単に「
メモリモデル 」と呼びます。
このセマンティックは、マルチスレッドプログラムの実行方法を規定していません。 むしろ、マルチスレッドプログラムが示す可能性のある動作を説明しています。 可能な動作パターンを生成する実行戦略はすべて受け入れられます。
17.1同期(17.1。同期)Javaプログラミング言語は、スレッド間で対話するための多くのメカニズムを提供します。 これらの最も基本的なものは、モニターを使用して実装される同期方法です。 Javaの各オブジェクトはモニターに関連付けられており、スレッドはこれをキャプチャまたはリリース(ロック/ロック解除)できます。 一度に1つのスレッドのみがモニターを保持できます。 このモニターをキャプチャしようとする他のスレッドは、キャプチャできるまでブロックされます。 スレッド
tは、特定のモニターを何度もブロックできます。 モニターが解放(ロック解除)されると、1つのロック操作の効果が取り消されます。
synchronizedステートメント(
§14.19 )はオブジェクトへの参照を計算し、そのオブジェクトのモニターをロックしようとしますが、キャプチャが成功するまで何も起こりません。 ロックが成功すると、ステートメントの同期された本体が実行されます。 同期されたオペレーターの本体が完全な形または省略形で実行されると、このモニターは自動的に解放(ロック解除)されます。
同期メソッド(
8.4.3.6 )は呼び出されると自動的に
ロックされ、その本体はロックが正常に完了するまで実行されません。 インスタンスメソッドを処理している場合、呼び出されたインスタンス(つまり、メソッド本体の実行中にこれと呼ばれるオブジェクト)に関連付けられたモニターをキャプチャします。 メソッドが静的な場合、メソッドが定義されているクラスを表すClassオブジェクトに関連付けられたモニターをキャプチャします。 メソッド本体の実行が完全にまたは簡略化された形式で完了すると、このモニターは自動的に解放されます。
Javaプログラミング言語は、デッドロック状態の定義を防止せず、定義する必要もありません。 スレッドが(直接または間接的に)複数のオブジェクトをキャプチャするプログラムは、デッドロックを回避するために一般的なトリックを使用する必要があります。 必要に応じて、デッドロックのない高レベルのロックプリミティブを作成します。
揮発性変数の読み取りや書き込み、java.util.concurrentパッケージのクラスの使用など、その他のメカニズムは、代替の同期方法を提供します。
17.2待機セットと通知各オブジェクトは、モニターに関連付けられているだけでなく、待機セットにも関連付けられています。 一連の期待は、一連のスレッドです。
オブジェクトが最初に作成されたとき、その一連の期待値は空です。 一連の期待値に対してスレッドを追加または削除する基本アクションはアトミックです。 一連の期待値は、メソッド
Object.wait 、
Object.notify 、および
Object.notifyAllによってのみ制御されます。
一連の期待値の操作は、スレッドの静的な中断と、中断に関連する
Threadクラスのメソッドによっても影響を受ける可能性があります。 さらに、他のスレッドを
スリープおよび
結合する
Threadクラスのメソッドには、
待機メソッドと
通知メソッドのアクションのプロパティから取得したプロパティがあります。
17.2.1。 待機中(17.2.1。待機)待機アクションは、
wait()メソッドが呼び出されたとき、または一時署名が
wait(長いミリ秒)および
wait(長いミリ秒、intナノ秒)のときに発生します。
パラメーターがゼロの
wait(長いミリ秒)呼び出し、または2つのパラメーターがゼロに指定された
wait(長いミリ秒、intナノ秒)呼び出しは、
wait()呼び出しと同等です。
スレッドは、
InterruptedExceptionをスローしない場合に戻り、待機します。
スレッド
tがオブジェクト
mで
waitメソッドを実行し、ロックされていないアクションにマッピングされなかった、
m ごとのtでブロックされたアクションの数を
nとします。 次のいずれかのアクションが発生します。
- nがゼロの場合(つまり、 tスレッドがターゲットmオブジェクトのロックをまだ取得していない場合)、 IllegalMonitorStateExceptionがスローされます。
- 指定された拍子記号でのこの待機が0〜999999の範囲にないnanosecs引数である場合、またはミリ秒引数が負の数で指定されている場合、 IllegalArgumentExceptionがスローされます。
- スレッドtが中断されると、 InterruptedExceptionがスローされ、中断ステータスtが falseに設定されます。
- それ以外の場合、次のシーケンスが保持されます。
- スレッドtは 、オブジェクトmの待機セットに追加され、Mでn個のロック解除を実行します。
- スレッドtは、オブジェクトmの待機セットから削除されるまで、命令に従いません。 スレッドは、次のいずれかの理由で待機セットから削除でき、後で復元されます。
- 通知アクションがmで実行されました。ここで 、 tは期待値のセットから削除されるように選択されています。
- mで実行されるNotifyAllアクション。
- 割り込みアクションはtで実行されます。
- 特定の拍子記号で待機する場合、内部アクションは、この待機アクションの開始後少なくともミリ秒とナノ 秒後に発生する一連の期待値mからtを削除します。
- 実装による内部アクション。 実装は推奨されませんが、「スプリアスウェイクアップ」を実行できます。つまり、スレッドを期待セットから削除し、追加の指示なしにアクションを再開できます。
スレッドがロックを保持しているという論理条件の下でのみ終了するループ内でのみ待機を使用する場合、この状況ではJavaでのコーディングが必要になることに注意してください。
各スレッドは、一連の予想からそれを削除するイベント(つまり、このスレッド)の順序を決定する必要があります。 この順序は他の順序と一致してはなりませんが、スレッドはこれらのイベントがこの順序で発生したかのように動作する必要があります。
たとえば、スレッドtが mの予測セットに含まれている場合、 tが中断されて通知されます。 これらのイベントは、何らかの順序で発生するはずです。 最初に割り込みが発生したと仮定した場合、 tは最終的にInterruptedExceptionで 待機から戻り、 待機セットmの他のスレッド(通知時に存在する場合)は通知を受け取る必要があります。 通知が最初に発生したと仮定した場合、通常の方法でtは最終的に待機から戻り、割り込みはスタンバイモードになります。
- スレッドtはmで nロックを実行します。
- スレッドtが割り込みのためにステップ2で一連の期待値mから削除された場合、割り込みステータスtは falseに設定され、 waitメソッドはInterruptedExceptionを要求します。
17.2.2。 通知(17.2.2。通知)notifyおよび
notifyAllメソッドが
呼び出されると、
通知が発生します。
スレッド
tがオブジェクト
mでこれらのメソッドのいずれかを使用し、
nを
t x mのロックロックの数とし、これがモニターリリースアクション(ロック解除)の数に対応しなかったとします。
次のいずれかのアクションが発生します。
- nがゼロの場合、 IllegalMonitorStateExceptionがスローされます。
これは、スレッドtがターゲットmオブジェクトのロックを失った場合です。 - nがゼロより大きく、これが通知アクションである場合、期待値セットmが空でない場合、現在の期待値セットmのメンバーであるスレッドuが選択され、期待値セットから削除されます。
一連の予想からどのスレッドが選択されるかは保証されません。 一連の期待値からスレッドuを削除すると、待機アクションでuが再開されます。 ただし、キャプチャアクションuは、再開されると、 tがmのモニターを完全にロック解除した後しばらくして実行されることに注意してください。 - nがゼロより大きく、 notifyAllアクションが実行されると、すべてのスレッドが期待値セットmから削除され、再開されます。
ただし、同時に、待機を再開している間、そのうちの1つだけが目的のモニターをキャプチャすることに注意してください。
17.2.3。 中断(17.2.3。中断)割り込みは、
Thread.interruptが呼び出されたときに発生します。また、
ThreadGroup.interruptなど、順番に呼び出すことを意図したメソッドも呼び出されます。
tと
uを同じにすることができるスレッド
uに対して、
tを
u.interruptに呼び出します。 これらのアクションは、割り込みステータス
uをtrueに設定します。
さらに、期待値のセットに
uが含まれるオブジェクト
mがある場合、
uは期待値のセット
mから削除されます。 これには、待機アクションを再開する
uが含まれます。この場合、モニター
mを再キャプチャした後
、 InterruptedExceptionがスローされます。
Thread.isInterruptedを呼び出すと、スレッドの中止ステータスを判断できます。 静的な
Thread.interruptedメソッドをスレッドで呼び出して、独自の割り込みステータスを監視およびクリアできます。
17.2.4。 待機、通知、および中断の相互作用(17.2.4。待機、通知、および中断の相互作用)上記の仕様により、期待、通知、中断の相互作用に関連するいくつかのプロパティを定義できます。
スレッドが待機中に通知されて中断された場合、次のいずれかを実行できます。
- 割り込みスタンバイモードのままでスタンバイに通常戻ります(つまり、 Thread.interruptedを呼び出すとtrueが返されます)
- InterruptedExceptionで待機から復帰します
スレッドはこの割り込み状態をリセットせず、待機呼び出しから正常に戻ることができません。
同様に、通知が中断のために失われることはありません。 オブジェクト
mの一連の期待に含まれる
sの一連
のスレッドと、別のスレッドが
mで 通知するとします。 その後、次のいずれか:
- 少なくとも1つのスレッドとsが正常に戻って待機するか、または
- sのすべてのスレッドは終了し、 InterruptedExceptionをスローする必要があります
スレッドがnotifyを介して中止およびウェイクアップされ、
InterruptedExceptionをスローして待機から復帰した場合、待機セット内の他のスレッドに通知する必要があることに注意してください。
17.3。 睡眠と歩留まり(17.3。睡眠と歩留まり)Thread.sleepは、システムタイマーとシステムスケジューラの精度に応じて、一定期間、作業スレッドをスリープモード(実行の一時的な終了)にします。 スレッドはモニターの制御を失うことはなく、スレッドを実行できるプロセッサーの計画と可用性に応じて、そのアクションが再開されます。
Thread.sleepも
Thread.yieldも同期セマンティクスを持たないことに言及することが重要です。 特に、コンパイラは
Thread.sleepまたは
Thread.yieldを呼び出す前に共有メモリ外のレジスタにキャッシュを
書き込まないで
ください 。また、コンパイラは
Thread.sleepまたは
Thread.yieldを呼び出した後にキャッシュレジスタをオーバーロードしないで
ください 。
たとえば、コードの次の(誤った)セグメント、this.doneが不揮発性のブールフィールドであるとします。
while (!this.done) Thread.sleep(1000);
コンパイラはthis.doneをキャッシュで1回だけ読み取り、それ以降はループの反復ごとにキャッシュ値を使用します。 これは、別のスレッドがthis.doneの値を変更した場合でも、サイクルが終了しないことを意味します。
以下の部分が表示されます。
パート2)メモリモデル。
パート3)最終フィールドのセマンティクス。 一部のプロセッサーでのワードティアリング(x32);
doubleおよび
longの非アトミックサポート。
ご清聴ありがとうございました!:)