第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の非アトミックサポート。
ご清聴ありがとうございました!:)