Cプログラムコードでは、オペレーティングシステムが提供するMutexロックを使用して、同期ブロックとスレッドブロックおよびウェイクアップ作業へのMutexアクセスを実現できます。ただし、Javaでは、Lockapiの提供に加えて、同期されたキーワードも構文レベルで提供され、Mutex同期プリミティブを実装しています。では、JVMに同期されたキーをどのように実装しますか?
1。同期されたバイトコード表現:
Java言語には、2つの組み込みの同期構文があります。1。同期されたステートメント。 2。同期されたメソッド。同期されたステートメントの場合、JavaソースコードがJavacによってBytecodeにコンパイルされた場合、MonitorenterおよびMonitorexit Bytecode命令は、同期ブロックの入力位置と終了位置にそれぞれ挿入されます。同期されたメソッドは、invokeVirtualおよびareturn命令などの通常のメソッド呼び出しおよび返品命令に変換されます。同期された修正方法を実装するためのVMバイトコードレベルでは、特別な命令はありません。代わりに、メソッドAccess_Flagsフィールドの同期フラグ位置1は、クラスファイルのメソッドテーブルに配置され、メソッドが同期されたメソッドであり、メソッドまたはメソッドに属するクラスを呼び出してKlassをロックオブジェクトとして表すオブジェクトを使用します。
2。JVMのロックの最適化:
JVMの単純に、MonitorEnter、Monitorexit Bytecodeは、基礎となるオペレーティングシステムのMutexLockに依存して実装しています。ただし、MutexLockを使用するには、現在のスレッドを一時停止し、ユーザー状態からカーネル状態に切り替える必要があるため、この切り替えは非常に高価です。ただし、ほとんどの場合、実際には、同期方法は単一の読み取り環境(ロックレス競争環境)で実行されます。 MutexLockが毎回呼び出された場合、プログラムのパフォーマンスに深刻な影響を与えます。ただし、JDK1.6では、ロックコアリング、ロックエリミネーション、軽量ロック、バイアスロック、適応型回転、その他のテクノロジーなどのロックの実装に多くの最適化が導入されています。
ロックコアーニング:つまり、不必要なロックおよびロック操作を削減し、複数の連続ロックを範囲の範囲のロックに拡張します。
ロックエリミネーション:ランタイムJITコンパイラによるエスケープ分析により、一部のロック保護が排除されます。現在の同期ブロックの外側の他のスレッドによって共有されない一部のデータ。エスケープ分析により、オブジェクトスペースをスレッドローカルスタックに割り当てることができます(同時に、ヒープのオーバーヘッドのゴミ収集を減らすこともできます)。
LightWeightLocking:このロックの実装は、実際の場合、プログラムのほとんどの同期コードが一般にロックフリーの競合状態(つまり、単一スレッド実行環境)にあるという仮定に基づいています。ロックフリーの競争の場合、オペレーティングシステムレベルでヘビー級ミューテックスを呼び出すことを完全に避けることができます。代わりに、MonitorEnterとMonitorexitでは、ロックの取得と解放を完了するためにCAS原子指示に依存するだけです。ロック競合がある場合、CAS命令を実行できないスレッドは、オペレーティングシステムミューテックスを呼び出してブロッキング状態を入力し、ロックがリリースされたときに目覚めます(特定の処理手順について詳しく説明します)。
バイアスロッキング:ロックフリーの競争の場合、ロックの取得中に不必要なCAS原子指示の実行を避けるためです。なぜなら、CAS原子指示はヘビー級ロックと比較してコストが比較的少ないが、まだかなりの局所的な遅延があるからです(この記事を参照)。
アダプティブスピニング:軽量ロックの取得中にスレッドがCAS操作を実行できない場合、モニターに関連付けられたオペレーティングシステムヘビー級ロック(MutexSemaphore)に入る前に忙しい待機に入り、再試行します。特定の数の試行の後も故障した場合、モニターに関連付けられたセマフォ(つまり、Mutex Lock)が呼び出され、ブロッキング状態に入ります。
3。Objectheader:
JVMにオブジェクトを作成すると、オブジェクトの前に2つの単語サイズのオブジェクトヘッダーが追加されます。 32ビットマシンの1つの単語は32ビットです。さまざまな内容物が、さまざまなステータスビットに従ってマークワールドに保存されます。上記の図に示すように、軽量のロックでは、MarkWordは2つの部分に分割されます。最初はロックワードがハッシュコードに設定され、最低3ビットはロックワードがある状態を表します。初期状態は001であり、これはロックフリー状態を意味します。 Klassptrは、クラスバイトコードが仮想マシン内にあるオブジェクトによって表されるアドレスを指します。フィールドは、連続オブジェクトインスタンスフィールドを表します。
4。モニターコード:
MonitorreCordは、スレッドのプライベートデータ構造です。各スレッドには、利用可能なモニターコードのリストとグローバル利用可能なリストがあります。では、これらのモニターコードの使用は何ですか?各ロックされたオブジェクトは、モニターコードに関連付けられます(オブジェクトヘッダーのロックワードはモニターコードの開始アドレスを指します。このアドレスは8byteアライメントされているため、ロックワードの最低3ビットをステータスビットとして使用できます)。同時に、モニターコードには、ロックを所有するスレッドの一意の識別子を保存するための所有者フィールドがあり、ロックがこのスレッドで占められていることを示しています。次の図は、モニターコードの内部構造を示しています。
所有者:最初のnullは、現在モニターレコードを所有しているスレッドがないことを意味します。スレッドがロックを正常に所有すると、スレッドの一意のアイデンティティを保存し、ロックが解放されると、nullに設定されます。
EntryQ:システムMutex(Semaphore)を関連付けて、モニターレコードのロックに失敗したすべてのスレッドをブロックします。
rcthis:モニターレコードでブロックされている、または待機しているすべてのスレッドの数を表します。
巣:再入国ロックのカウントを実装するために使用されます。
ハッシュコード:オブジェクトヘッダーからコピーされたハッシュコード値を保存します(GC年齢も含む場合があります)。
候補者:毎回ロックを正常に所有できるスレッドのみが1つのスレッドのみが使用できるため、不必要なブロッキングやスレッドが目覚めるのを待つために使用されます。ロックをリリースする以前のスレッドが毎回すべてのブロックまたは待機スレッドを目覚めさせると、不必要なコンテキストの切り替え(競合ロックの故障のためにブロッキングから準備が整ってから再びブロックされます)を引き起こし、したがって深刻なパフォーマンスの劣化につながります。候補者には可能な値が2つしかありません。0つのことは、ロックを競うために後継者のスレッドを目覚めるための1つの手段を目覚める必要があるスレッドがないことを意味します。
5。軽量ロックの特定の実装:
スレッドは2つの方法でオブジェクトをロックできます。1。オブジェクトをロックフリー状態に拡張してオブジェクトのロックを取得します(ステータスビット001)。 2。オブジェクトはすでに拡張された状態(ステータスビット00)にありますが、ロックワードで指摘されたモニターレコードの所有者フィールドはnullであるため、ロックを取得するためにCAS原子指示を通じて所有者を独自のアイデンティティに直接設定することができます。
ロック(MonitorEnter)を取得する一般的なプロセスは次のとおりです。
(1)オブジェクトがロックフリー状態にある場合(RecordWord値はハッシュコード、ステータスビットは001)、スレッドは最初に使用可能なモニターレコードリストから無料のモニターレコードを取得します。初期の巣と所有者の値は、それぞれ1とスレッド自身の識別をプリセットします。モニターレコードの準備ができたら、モニターレコードのオブジェクトヘッダーのロックワードフィールドにCAS原子命令を介してロックワードフィールドにインストールして拡張します(元のテキストが膨らんでいます。膨張と呼ばれる理由は、主にオブジェクトが拡張された後に拡張される理由です。 2つの実装方法はわずかに異なります。
(2)オブジェクトが拡張され、所有者に保存されたスレッドがロック自体を取得するスレッドとして識別されます。これは、リエントラントロックの場合です。単に1つをネストに追加する必要があります。原子動作は不要で、非常に効率的です。
(3)オブジェクトは拡張されていますが、所有者の値はnullです。この状態は、スレッドのブロックまたはロックの待機が同時にロックされているときに発生します。ロックの前の所有者がロックを放出したばかりです。この時点で、複数のスレッドは、マルチスレッドの競争状態でロックを取得するために、CAS原子指示を通じて所有者を自分のアイデンティティに設定しようとします。競合できないスレッドは、4番目のケースの実行パスに入ります(4)。
(4)オブジェクトは拡張状態にあり、所有者はnull(ロック)ではありません。オペレーティングシステムのヘビー級ミューテックスを呼び出す前に、特定の回数を回転させます。特定の回数に達した場合、ロックがまだ正常に取得されていない場合、ブロッキング状態に入り始める時が来ました。まず、RFSの値を原子的に1だけ追加します。他のスレッドは、1の追加中にオブジェクトとモニターレコードの関係を破壊する可能性があるため、ロックワードの値が変更されていないことを確認するために、1を追加した後に別の比較を実行する必要があります。変更されたことがわかった場合、MonitorEnterプロセスを繰り返す必要があります。同時に、所有者がnullであるかどうかは再び観察されます。もしそうなら、CASに競争ロックに参加するように呼びかけます。ロック競合が失敗した場合、ブロッキング状態に入ります。
ロックを解放する一般的なプロセス(Monitorexit)は次のとおりです。
(1)最初に、オブジェクトが拡張状態にあるかどうかを確認し、スレッドがロックの所有者であるかどうかを確認します。それが間違っていることが判明した場合、例外がスローされます。
(2)巣のフィールドが1を超えるかどうかを確認します。1を超える場合は、単に巣を1削減し、ロックを維持し続けます。 1に等しい場合は、ステップ(3)を入力します。
(3)RFSTISが0より大きいかどうかを確認し、所有者をnullに設定し、ブロッキングまたは待機スレッドを起動してロックを再度取得しようとします。 0に等しい場合、ステップ(4)に入ります
(4)オブジェクトをデフレートし、オブジェクトのロックワードを元のハッシュコード値に戻してロックをリリースしてロックを解放し、モニターレコードをスレッドに戻します。
要約します
参照:「 Java Virtual Machine JVM(Zhou Zhiming)の高度な機能とベストプラクティスの詳細な理解」
上記は、JVMの詳細の同期と実装の問題分析に関するこの記事の内容全体です。私はそれが誰にでも役立つことを願っています。欠点がある場合は、それを指摘するためにメッセージを残してください。このサイトへのご支援をありがとうございました!