マルチスレッドの問題(パフォーマンスまたはあいまいなハイゼンバッグ)に直面しているすべての人は、それらを解決する過程で、必然的にインフレーション、競合、メンバー、バイアスロック、スレッドパーキングなどの用語に遭遇します。 しかし、誰もがこれらの用語の背後に隠されているものを本当に知っていますか? 残念ながら、実践が示すように、 すべてではありません 。
状況を修正することを期待して、私はこのトピックに関する一連の記事を書くことにしました。 それらはそれぞれ、 「最初に理論的に何が起こるべきかを簡単に説明し、次にソースに行き、そこでどうなるかを見る」という原則に基づいて構築されます。 したがって、最初の部分はJavaだけでなく、主に適用可能であるため、他のプラットフォームの開発者は自分にとって有益なものを見つけることができます。
詳細な説明を読む前に、Javaメモリモデルに精通していることを確認しておくと役立ちます。 たとえば、Sergey Walrus Kuksenkoのスライドや、私の初期のトピックで学習できます 。 また、スライド#38から始まるこのプレゼンテーションは素晴らしい資料です。
理論的最小値
ご存じのように、javaの各オブジェクトには独自のmonitorがあるため、同じC ++とは異なり、個別のmutex-sを使用してオブジェクトへのアクセスを保護する必要はありません。 フローの相互排除と同期の効果を実現するには、次の操作が使用されます。- monitorenter :キャプチャを監視します。 一度に1つのスレッドのみがモニターを所有できます。 キャプチャの試行時にモニターがビジーの場合、キャプチャしようとするスレッドは解放されるまで待機します。 この場合、キューには複数のスレッドが存在する可能性があります。
- monitorexit :無料のモニター
- wait :現在のスレッドをモニターのいわゆる待機セットに移動し、
notify
発生を待機します。wait
メソッドの終了もfalseである可能性があります。 モニターを所有するスレッドwait
と、他のスレッドがモニターを引き継ぐことができます。 - notify (all) :現在モニターの待機セットにあるスレッドの1つ(またはすべて)を起動します。 制御を取得するには、目覚めたストリームがモニターを正常にキャプチャする必要があります(monitorenter)
続行する前に、重要な概念を定義します。
競合 -複数のエンティティが同時に同じリソースを所有しようとする状況。排他的使用を目的としています
モニターの所持に競合があるという事実は、モニターのキャプチャ方法に大きく依存します。 モニターの状態は次のとおりです。
- init :モニターは作成されたばかりで、これまで誰もキャプチャしていません
- バイアス :( スマート最適化、すぐにはほど遠いように見えました)モニターは、それをキャプチャした最初のスレッド用に「予約」されています。 将来、このストリームはキャプチャするのに高価な操作を必要とせず、キャプチャは非常に迅速に行われます。 キャプチャが別のストリームを生成しようとすると、モニターはそのストリームのために予約されます( rebias )、またはモニターがシン状態になります( バイアスを取り消します )。 モニターがキャプチャーしようとしているオブジェクトのクラスのすべてのインスタンスに即座に作用する追加の最適化もあります( 一括取り消し/バイアス )
- thin :複数のストリームがモニターをキャプチャしようとしますが、競合はありません(つまり、同時にキャプチャしたり、非常に小さなオーバーラップでキャプチャしたりすることはありません) 。 この場合、キャプチャは比較的安価なCASを使用して実行されます。 競合が発生すると、モニターは膨張状態に入ります
- fat / inflated :オペレーティングシステムレベルで同期が行われます。 ストリームは、モニターをキャプチャする順番になるまで待機してスリープします。 コンテキストを変更するコストを忘れたとしても、スレッドが制御を取得するときは、システムシェダラーにも依存するため、必要以上に時間がかかります。 競合が解消されると、モニターは薄い状態に戻る場合があります
この抽象的な推論で終わり、ホットスポットでの実装方法に没頭しています。
オブジェクトヘッダー
仮想マシン内のオブジェクトヘッダーには、通常、 マークワードとオブジェクトクラスへのポインターという 2つのフィールドが含まれます。 特別な場合には、たとえば配列の長さなど、そこに何かを追加できます。 これらのヘッダーは、いわゆるoop (通常のオブジェクトポインター)に格納され、ファイルhotspot/src/share/vm/oops/oop.hpp
でその構造を見ることができます。 同じフォルダーにある
markOop.hpp
ファイルに記述されているマークワードとは何かをより詳しく調べます。 (
oopDesc
からの継承には注意を払わないでください:これは歴史的な理由のみです)良い方法で、詳細なコメントに注意を払って注意深く読む必要がありますが、あまり興味がない人のために、このマークの内容の簡単な説明を以下に示します単語が含まれているとどのような場合に。 このプレゼンテーションは、90番目のスライドから引き続き見ることができます。
単語の内容をマークする
状態 | タグ付け | 内容 | |||
ロックされていない、薄く、偏っていない | 01 | |
|
| |
ロックされ、薄く、偏りがない | 00 |
| |||
膨らんだ | 10 | ||||
偏った | 01 | |
|
|
|
gcのマーク | 11 |
ここにいくつかの新しい意味があります。 まず、 IDハッシュコードは、
System.identityHashCode
呼び出されたときに返されるオブジェクトのハッシュコードです。 第二に、 年齢はオブジェクトが生き残ったガベージコレクションの数です。 また、このオブジェクトのクラスのバルク失効またはバルクバイアスの数を示すエポックもあります。 これが必要な理由については、後で説明します。
バイアスの場合、アイデンティティハッシュコードとthreadID +エポックの両方に十分なスペースが同時にないことに気づきましたか? そして、これはそうであり、ここから興味深い結果があります:ホットスポットでは、さらに、モニターがビジーの場合、マークワードは、実際のマークワードが保存されているマークワードに保存されます。 各スレッドのスタックには、さまざまなものが格納されるいくつかの「セクション」があります。 ロックレコードが保存されるものに興味があります。 そこで、オブジェクトのマークワードを軽量ロックでコピーします。 したがって、ちなみに、シンロックされたオブジェクトはスタックロックと呼ばれます 。 腫れたモニターは、膨張したストリームと、シックモニターのグローバルプールの両方に保存できます。System.identityHashCode
を呼び出すと、オブジェクトの取り消しバイアスが発生します。
それでは、コードに行きましょう。
synchronized
を使用する簡単な例
このクラスから始めましょう:
| |
それが何にコンパイルされるかを見てください:
javac SynchronizedSample.java && javap -c SynchronizedSample
完全なリストは提供しませんが、
doSomething
メソッドの本体を使用して、コメントを提供します。
void doSomething(); Code: 0: aload_0 // this 1: dup // (this) 2: astore_1 // (this) 1 3: monitorenter // , (this) 4: aload_1 // this 5: monitorexit // 6: goto 14 // "catch-" 9: astore_2 // ( , ) 2 10: aload_1 // this 11: monitorexit // 12: aload_2 // 13: athrow // 14: return // Exception table: from to target type 4 6 9 any // , "catch-" 9 12 9 any // ,
ここでは、
monitorenter
および
monitorexit
興味があり
monitorenter
。 もちろん、あなたが選んだ
一般に、一部のアクションのVMヘルパーのコードは、JITによって貼り付けられたものと内容が異なる場合があります。 JITの一部の最適化がインタープリターに移植されない可能性がある点まで
また、リョーシャはPrintAssemblyを自分の歯に入れて、コンパイル済みのjitコード化されたコードを確認することを推奨しましたが、抵抗が少ないパスから始めて、それが本当にtmであるかを確認することにしました
モニター
インタプリタのソースは
hotspot/src/share/vm/interpreter
にあり、それらの多くがあります。 この段階ですべてを読み直すことはあまりお勧めできません。grepを使用すると、必要な場所が見つかる可能性があるためです。 まず、
bytecodes.hpp
と
bytecodes.cpp
発表を見る価値があります。
./bytecodes.hpp:235: _monitorenter = 194, // 0xc2 ./bytecodes.cpp:489: def(_monitorenter, "monitorenter", "b", NULL, T_VOID, -1, true);
簡単に推測できるように、バイトコード
.hpp
人間の列挙定数は
.hpp
で定義され、この操作はdefメソッドを使用して
.cpp
登録されます。 この記事のフレームワークでそれについて個別に話す意味はありません:monitorenterコマンドが
monitorenter
ていることを明確にするだけで十分です。これは、パラメーター(
b
)のない単一のバイトコードであり、何も返さず、スタックから1つの値を引き出し、ロックを呼び出したり、セーフポイントを呼び出したりすることができます(後者については後で)。
以下は、
bytecodeInterpreter.cpp
ファイルに関係します。 すばらしい
BytecodeInterpreter::run(interpreterState istate)
メソッド
BytecodeInterpreter::run(interpreterState istate)
、これは約2200行しか必要とせず、通常、処理されたメソッドの本体が終了するまでループで回転します。 (実際、別の大きな部分は、メソッドの初期化、メソッドが
synchronized
場合のロックなど、他の有用なものを扱います)。 最後に、行
1667
から、
monitorenter
操作が発生したときに何が起こるかを
monitorenter
ます。 まず、ストリームスタックに無料のモニターがあり(ない場合は
istate->set_msg(more_monitors)
を使用してインタープリターから要求され
istate->set_msg(more_monitors)
、ロック解除されたマークワードのコピーがそこに配置されます。 その後、CASを使用して、オブジェクトのマークワードにこのコピーへのポインターを書き込もうとします。これは、 displaced headerと呼ばれます。
CAS-比較と交換-*dest
とcompare_value
原子的に比較し、等しい場合は*dest
とexchange_value
場所で比較exchange_value
ます。 初期値*dest
返されます。 (同時に、両面membarは保証されますが、次の記事でそれらについて詳しく説明します)
CASが成功した場合、勝利(およびそれによってモニター)は私たちのものであり、そこで終了できます(タグは、置き換えられたヘッダーへのポインター自体に含まれています-最適化)。 そうでない場合は、次に進みますが、最初に重要な点に注意を払います。 このモニターにバイアスがかかっているかどうかを確認しませんでした 。 Lyoshinの警告を思い出して、通訳には届かない最適化に出会ったことがわかります。 ところで、
synchronized
メソッドを処理するとき、すべてが正常にチェックされますが、それは少し後です。
CASが失敗した場合、モニターの所有者であるかどうかを確認します(再帰キャプチャ)。 もしそうなら、成功は再び私たちのものであり、私たちがすることはスタックの
NULL
置き換えられたヘッダーに書き込むことだけです(後でそれが必要な理由がわかります)。 それ以外の場合は、次の呼び出しを行います。
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
マクロ
CALL_VM
は、フレームの作成など、あらゆる種類の技術的な操作を実行し、転送された機能を実行します。 私たちの場合、この関数は
InterpreterRuntime::monitorenter
であり、これは新しい
interpreterRuntime.cpp
ファイルにあります。
monitorenter
メソッドは、
UseBiasedLocking
が
UseBiasedLocking
れているかどうかに応じて、
ObjectSynchronizer::fast_enter
または
ObjectSynchronizer::slow_enter
ます。 最初のものから始めましょう。
fast_enter
まず、そのような最適化の適切性に関するいくつかの言葉。 IBM Research Labsの一部の東京の科学者は、未知の方法で統計を計算し、実際、ほとんどの場合、同期は競合しないことを発見しました。 さらに、さらに強力な声明が提案されました。ほとんどのオブジェクトのモニターは、生涯を通じて1つのストリームのみでキャプチャされます。 そして、偏りのあるロックのアイデアが生まれました。最初に起きた人、それ、そしてスリッパです。 バイアスロックの詳細については、たとえばこれらのスライドで読むことができます(ただし、多少古くなっています)。ホットスポットのソースに戻ります。 ここで、ファイル
src/share/vm/runtime/synchronizer.cpp
に興味があります。169行目から始め
src/share/vm/runtime/synchronizer.cpp
。最初に、自分自身にバイアスをかけ、うまくいかない場合は、取り消しを行い、通常のthin slow_enterに移動します。 楽観的な試みは、
biasedLocking.cpp
ファイルにある
BiasedLocking::revoke_and_rebias
で発生します。 より詳細に説明しましょう。
- Rebias-モニターをすばやくキャプチャする権利を留保するストリームの変更。 単純な場合に許可されます。たとえば、モニターが実際にキャプチャできなかった場合( 匿名バイアス )、またはバイアスが前の時代から保持されている場合(特定のクラスの一括取り消しの回数、以下を参照)、本質的には同様です誰もバイアスを監視していない。 さらに、fast_enterを呼び出すときに
fast_enter
完全に無効にすることができます(attempt_rebias = false
)。 - 取り消し -モニターのバイアスロックを無効にします。 ほとんどの場合、特に
attempt_rebias
false
設定されている場合に使用されfalse
。 現在、モニターが別のライブストリームによって所有されている場合、rebiasはこのスレッドがsafepointで停止することを要求します。その後、このストリームのスタックを調べ 、そこに格納されたモニターヘッダーをバイアスなしに修正します。 - バルクリバイアス -このクラスのすべての生きているインスタンスと後のインスタンスのリバイアス。 この時代の特定のクラスの取り消しまたはリバイアスの数がBiasedLockingBulkRebiasThresholdを超えると
BiasedLockingBulkRebiasThreshold
ます。 グローバルセーフポイントで実行される時代の変化を引き起こします。 - 一括取り消し -このクラスのすべての生きていて割り当てられた後のインスタンスを取り消します。 この時代の特定のクラスの失効またはリバイアスの回数がBiasedLockingBulkRevokeThresholdを超えると
BiasedLockingBulkRevokeThreshold
ます。 グローバルセーフポイントで実行される時代の変化を引き起こします。
私は知っていたが、 tmを忘れてしまった人々に思い出させます。
safepoint-スレッドの実行が安全な場所で停止している仮想マシンの状態。 これにより、スレッドが現在所有しているモニターのバイアスの取り消し、最適化解除、スレッドダンプの取得など、侵入的な操作が可能になります。
この場合、
attempt_rebias
パラメーターは常に
true
、たとえばVMスレッドからの呼び出しの場合など、
false
になることがあります。
ご想像のとおり、バルク操作は、スレッド間で多数のオブジェクトを簡単に転送できるようにするためのトリッキーな最適化です。 この最適化がなかった場合、デフォルトで
UseBiasedLocking
を有効にするのは危険です。なぜなら、アプリケーションの大きなクラスは失効と再バイアスを永遠に処理するからです。
ストリームをすばやくキャプチャできない(つまり、取り消しバイアスが行われた)場合は、シンロックのキャプチャに進みます。
slow_enter
ここで興味のあるメソッドは、
src/share/vm/runtime/synchronizer.cpp
ファイルにあります。 ここにいくつかのシナリオがあります。
- ケース1、 「良い」 :オブジェクトのモニターは現在無料です。 次に、CASを使用して占有しようとします。 CASは既にアーキテクチャに依存する(通常はプロセッサによってネイティブにサポートされる)命令であるため、さらに深くする意味はありません。 (Ruslan#は彼にケレミンヘンウィートを与えましたが 、私は「MOSトランジスタのソースシンク内の電子の流れに到達しなかった」とinしました 。 ) CASが成功すると、勝利:モニターがキャプチャされます。 それが失敗すると、悲しみに満たされ、3番目のケースに進みます。
- ケース2も「良好」です。オブジェクトのモニターは無料ではありませんが、現在キャプチャしようとしている同じストリームでビジーです。 これは再帰的なキャプチャです。 この場合、簡単にするために、スタックNULLのディスプレイスされたヘッダーに書き込みます。これは、以前にこのモニターを既にキャプチャし、スタックに関する情報をすでに持っているためです。
- ケース3、 「悪い」膨張 :だから、失敗したトリックを見せようとする試みはすべて失敗しました。そして、下品なユーザー言語を見せて、セグメンテーション違反に陥る以外にやることはありません。 ハハ 冗談。 ただし、ふくれっ面については何も言いませんでした。OSレベルのプリミティブに頼って「正直に」行動しなければならない場合、 モニターが膨らんだと言います。 コードでは、この動作は
ObjectSynchronizer::inflate
で説明されていますが、ここではあまり注意しません。実際、このメソッドはスレッドセーフであり、いくつかの技術的な微妙な点を考慮して、フラグをモニターに設定します。
モニターを膨らませた後、モニターに入る必要があります。ObjectMonitor::enter
メソッドはまさにそれを行い、考えられない考えられないすべてのトリックを適用して、ストリームのObjectMonitor::enter
を回避します。 これらのトリックには、ご想像のとおり、1回限りのCAS-sやその他の「フリーメソッド」を使用して、スピンループを使用してキャプチャを試みることが含まれます。 ちなみに、コメントと何が起こっているのかとの間にわずかな矛盾が見つかったようです。 一度スピンループでモニターに入ろうとすると、一度だけ行うと主張します。
352 353 354 355 356 357 358 359 360 361 362 363
// Try one round of spinning *before* enqueueing Self // and before going through the awkward and expensive state // transitions. The following spin is strictly optional ... // Note that if we acquire the monitor from an initial spin // we forgo posting JVMTI events and firing DTRACE probes. if (Knob_SpinEarly && TrySpin (Self) > 0) { assert (_owner == Self , "invariant") ; assert (_recursions == 0 , "invariant") ; assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ; Self->_Stalled = 0 ; return ; }
そして、ここでもう少し、呼び出されたenterI
メソッドで、もう一度実行します。
485 486 487 488 489 490 491 492 493 494 495 496 497
// We try one round of spinning *before* enqueueing Self. // // If the _owner is ready but OFFPROC we could use a YieldTo() // operation to donate the remainder of this thread's quantum // to the owner. This has subtle but beneficial affinity // effects. if (TrySpin (Self) > 0) { assert (_owner == Self , "invariant") ; assert (_succ != Self , "invariant") ; assert (_Responsible != Self , "invariant") ; return ; }
さて、オペレーティングシステムレベルでの駐車は非常に怖いので、ほとんど何でもそれを回避する準備ができています。 彼女の何がひどいのか見てみましょう。
逆駐車ストリーム
それでは、駐車ストリームとは正確には何ですか? 各モニターにはいわゆるEntry List ( Waitsetと混同しないでください )があることを誰もが聞いたことがあるはずです。 モニターへの低価格での入力試行がすべて失敗した後、この特定のキューに自分自身を追加し、その後、駐車します。
591 592 593 594 595 596 597 598 599 600 601
// park self if (_Responsible == Self || (SyncFlags & 1)) { TEVENT (Inflated enter - park TIMED) ; Self->_ParkEvent->park ((jlong) RecheckInterval) ; // Increase the RecheckInterval, but clamp the value. RecheckInterval *= 8 ; if (RecheckInterval > 1000) RecheckInterval = 1000 ; } else { TEVENT (Inflated enter - park UNTIMED) ; Self->_ParkEvent->park() ; }
駐車場に直接移動する前に、現在のストリームに責任があるかどうかに応じて、ここで時間を調整できるかどうかを確認します。 責任のあるフローは常に1つだけであり、いわゆるストランディングを回避するために必要です。モニターが空いているときの悲しみですが、待機セット内のすべてのフローはまだ待機しており、奇跡を待っています。 担当者がいるとき、彼は時々自動的に目を覚まします(より多くの無駄な目覚めが起きた-目覚めた後、ロックを取得できませんでした-駐車時間は長くなります。1000msを超えないことに注意してください)。 残りのフローは、少なくとも永遠に目覚めを待つことができます。
それでは、駐車の本質に移りましょう。 すでに理解しているように、これはにwait/notify
たwait/notify
Java開発者に似たセマンティクス上のものですが、オペレーティングシステムレベルで発生します。 たとえば、Linuxおよびbsdでは、ご想像のとおり、POSIXスレッドが使用されます。POSIXスレッドでは、pthread_cond_timedwait
(またはpthread_cond_wait
)が呼び出され、モニターが解放されるのを待ちます。 これらのメソッドは、LinuxスレッドのステータスをWAITINGに変更し、何らかのイベントが発生したときにシステムスケジューラにウェイクアップするように要求します(ただし、このスレッドが責任を負うのは一定期間後までです)。
OSレベルのスレッドスケジューリング
それでは、Linuxカーネルを調べて、そこでスケジューラーがどのように機能するかを見てみましょう。 ご存知のように、Linuxソースはgitにあり、次のようにシェダーをクローンできます。
git clone git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-rt.git
フォルダに行きましょう...わかりました、わかりました、冗談です。 ソースコードの行に対して正確なLinuxシェダーデバイスの機能は、単純なsynchronized
ブロックから始めた無害な記事には既に深すぎます。ハードコアを減らすために、シェダーが一般的にどのように機能するかを説明します。 linux, . - , , : — , . — quantum — , , , . linux , 10-200 , 1 . windows , 2-15 , — 10 15 .
, , , . , , (, - I/O-), , - . - . , , , -, , .
, , , — . , , , , , , .
, , : , — , . : , , -, .
, , , . , contention, .
monitorexit
バイアスロックの場合、基本的には何もしません。置き換えられたヘッダーにはNULLが格納されていることがわかり、終了します。これは興味深い点です。現在占有されていないバイアスロックを解除しようとすると、インタープリターはスローされません
IllegalMonitorStateException
(ただし、バイトコード検証はそのようなことを監視します)。
偏りのないロックの場合、を呼び出します
InterpreterRuntime::monitorexit
。いくつかのチェックの後(たとえば、モニターが実際にロックされているかどうか、そうでない場合はスローされます
IllegalMonitorStateException
)、呼び出され
ObjectSynchronizer::slow_exit
ます
fast_exit
。ソースを読む場合、高速パスに関するコメントに注意を払ってはいけません。この方法では、次のシナリオが可能です。モニターがスタックロック状態になっている、膨張または膨張。最初のケースでは、すべてが単純です。オブジェクトヘッダーをロック前の状態に戻し、終了します。 2番目のケースでは、誰かがモニターの膨らみを終えて3番目のケースに進むのを待ちます。
3番目のケースでは、ロックを解除し、膜を露出します。その後、現在ロックを取得する準備ができている駐車ストリームがあるかどうかを確認します。これは、彼が目を覚まして、たとえば
TrySpin
(上記を参照)を使用してモニターをキャプチャしようとする場合に可能です。これが見つかった場合、これに関する作業は完了します。ロックを受け取りたいスレッドのキューが空の場合も完了します。
そのようなフローがない場合は、ポリシーに応じて(を使用して設定
Knob_QMode
。正直なところ、その値が
0
デフォルト設定から変化する単一の場所を見つけていません。ただし、知識のある人tmは、これがデバッグとチューニングの残りである可能性が高いことを示唆しています)、最初に目を覚ます人を選択します。これらは最近目覚めたストリームかもしれませんし、逆に最近目覚めたストリームかもしれません。短い一連の呼び出しの後、プラットフォーム固有のコードを見つけます
os::PlatformEvent::unpark()
。このコードは、目的のストリームに信号を伝えます。たとえば、linuxとbsdが使用され
pthread_cond_signal
ます。
実際、あまり詳しく説明しなければ、モニターのリリースについて言えることはこれだけです。
NB:synchronized
メソッド
次のように元のJavaコードを記述した場合:
synchronized void doSomething() { // Do something }
このメソッドのバイトコードは著しく短くなります:
synchronized void doSomething(); Code: 0: return
イン
bytecodeInterpreter.cpp
synchronized
メソッドは、行から処理されます
767
。チェックがあり
if (METHOD->is_synchronized())
ます。この条件の内部には、
if
偏りのあるロックに関連する膨大な数のがあります。これは、操作を処理するときに突然表示されなかったもの
monitorenter
です。一般に、前に説明したことが行われていますが、所有者スレッドによるバイアスのかかったモニターの(CASなしの)迅速なキャプチャがまだあります。
また、メソッド本体の実行が終了した後、メソッドが同期されるとモニターは終了します。
待って通知する
これらのメソッドの処理は、
synchronizer.cpp
377行目から始まります。インタプリタで待機/通知しているモニターは、これらのメソッドが最初に行うことで膨らませるので、膨らませる必要があります。その後、彼らは彼のメソッド
wait
またはを呼び出します
notify
。
最初の1つは、待機セット(実際にはターン)に追加され、ウェイクアップする時間になるまで待機します(待機するように要求された時間が経過しました。中断または通知と呼ばれる人がいました)。
Notify
待機セットから1つのストリームをプルし、ポリシーに応じて、モニターをキャプチャしたい人のキューのある場所に追加します。
NotifyAll
全員が待機セットから抜け出すという点でのみ異なります。
メモリ効果
JMMに精通している人は、同じモニターをキャプチャする前にモニターのリリースが行われることを知っています。シンの場合、これはCASによって保証されます。膨らんだ場合、これは明示的な呼び出しによって保証されます
OrderAccess::fence();
。モニターが偏っている場合、それは1つのスレッドのみがモニターを使用することを意味します。その実行はすでにプログラムの順序で保証されています。HBを取り消すと、monitorexitの最中(ストリームが生きていた場合)(すでに薄いか膨張している)、または入力したとき(これも薄いか膨張している)が表示されます。
HBを保証するために、待機を終了する直前に明示的なフェンスが設定されます。
Major O. aka Disclaimer からのコメント
実際、tm、すべては私たちが考えているようには起こりません。たとえば、JITがコードをネイティブにコンパイルする場合。または、他の仮想マシンで作業する場合。ただし、「ホットスポットのインタープリター」という単純なケースで、すべてが実際に私がここで書いた方法であることを保証することはできません。お楽しみに
次のシリーズでは、まず、メモリーバリアについて説明する必要があります。これは、JMMで発生前を提供するために非常に重要です。これらはvolatile
、今後行うフィールドの例について検討するのに非常に便利です。最終フィールドと安全な出版物にも注意を払う価値がありますが、TheShadeとchereminはすでにそれらの 記事でそれらをカバーしているので、読書に興味のある人にそれらを読むことができます(慎重にのみ)。そして最後に、JITが入ってきたときの違いについて、PrintAssemblyでいっぱいのストーリーを待つことができます。
そしてもう一つ©
旅行を繰り返したい人のために:jdk7uの144f8a1a43cbリビジョンを使用しました。リビジョンが異なる場合、行番号も異なる場合があります-K.O.バイアスロックは、仮想マシンの起動直後ではなく、
BiasedLockingStartupDelay
ミリ秒(デフォルトでは4000)後にオンになります。これは、そうでない場合、仮想マシンの起動と初期化、クラスのロード、その他すべてのプロセスで、生きているオブジェクトの一定の取り消しバイアスが原因で膨大な数のセーフポイントが表示されるためです。
すべてのセーフポイントで、メソッドは
ObjectSynchronizer::deflate_idle_monitors
何をしているかを非常に簡単に理解できる名前で呼び出されます。
勇敢なTheShade、artyushov、cheremin、AlexeyTokarに感謝します 彼らが(あなたについて)出版前に記事を読み、愚かな冗談やandに満ちた光の代わりに、ナンセンスを大衆に持ち込まないことを確かめた。