Java言語仕様。 第17章スレッドとロック(翻訳。パート1)

こんにちは、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.waitObject.notify 、およびObject.notifyAllによってのみ制御されます。



一連の期待値の操作は、スレッドの静的な中断と、中断に関連するThreadクラスのメソッドによっても影響を受ける可能性があります。 さらに、他のスレッドをスリープおよび結合するThreadクラスのメソッドには、 待機メソッドと通知メソッドのアクションのプロパティから取得したプロパティがあります。



17.2.1。 待機中(17.2.1。待機)



待機アクションは、 wait()メソッドが呼び出されたとき、または一時署名がwait(長いミリ秒)およびwait(長いミリ秒、intナノ秒)のときに発生します。



パラメーターがゼロのwait(長いミリ秒)呼び出し、または2つのパラメーターがゼロに指定されたwait(長いミリ秒、intナノ秒)呼び出しは、 wait()呼び出しと同等です。



スレッドは、 InterruptedExceptionをスローしない場合に戻り、待機します。

スレッドtがオブジェクトmwaitメソッドを実行し、ロックされていないアクションにマッピングされなかった、 m ごとのtでブロックされたアクションの数をnとします。 次のいずれかのアクションが発生します。





17.2.2。 通知(17.2.2。通知)

notifyおよびnotifyAllメソッドが呼び出されると、 通知が発生します。

スレッドtがオブジェクトmでこれらのメソッドのいずれかを使用し、 nt x mのロックロックの数とし、これがモニターリリースアクション(ロック解除)の数に対応しなかったとします。

次のいずれかのアクションが発生します。





17.2.3。 中断(17.2.3。中断)

割り込みは、 Thread.interruptが呼び出されたときに発生します。また、 ThreadGroup.interruptなど、順番に呼び出すことを意図したメソッドも呼び出されます。

tuを同じにすることができるスレッドuに対して、 tu.interruptに呼び出します。 これらのアクションは、割り込みステータスuをtrueに設定します。



さらに、期待値のセットにuが含まれるオブジェクトmがある場合、 uは期待値のセットmから削除されます。 これには、待機アクションを再開するuが含まれます。この場合、モニターmを再キャプチャした後 InterruptedExceptionがスローされます。

Thread.isInterruptedを呼び出すと、スレッドの中止ステータスを判断できます。 静的なThread.interruptedメソッドをスレッドで呼び出して、独自の割り込みステータスを監視およびクリアできます。



17.2.4。 待機、通知、および中断の相互作用(17.2.4。待機、通知、および中断の相互作用)



上記の仕様により、期待、通知、中断の相互作用に関連するいくつかのプロパティを定義できます。



スレッドが待機中に通知されて中断された場合、次のいずれかを実行できます。





スレッドはこの割り込み状態をリセットせず、待機呼び出しから正常に戻ることができません。

同様に、通知が中断のために失われることはありません。 オブジェクトmの一連の期待に含まれるsの一連スレッドと、別のスレッドがmで 通知するとします。 その後、次のいずれか:





スレッドがnotifyを介して中止およびウェイクアップされ、 InterruptedExceptionをスローして待機から復帰した場合、待機セット内の他のスレッドに通知する必要があることに注意してください。



17.3。 睡眠と歩留まり(17.3。睡眠と歩留まり)



Thread.sleepは、システムタイマーとシステムスケジューラの精度に応じて、一定期間、作業スレッドをスリープモード(実行の一時的な終了)にします。 スレッドはモニターの制御を失うことはなく、スレッドを実行できるプロセッサーの計画と可用性に応じて、そのアクションが再開されます。

Thread.sleepThread.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の非アトミックサポート。



ご清聴ありがとうございました!:)



All Articles