ロックのアップグレードを完了するための 1 つの記事 (偏ったロック、軽量のロック、重量のロック)

前提知識: 同期

 JavaSE1.6 より前では、同期は重量ロックと呼ばれていました。しかし、JavaSE1.6では、同期化が最適化され、偏ったロックと軽量ロックが導入され、ロックのストレージ構造とアップグレードプロセスが導入され、ロックの取得と解放のパフォーマンス消費が削減されました。とても重い。

同期メソッドでは、フラグ マーク ACC_SYNCHRONIZED が使用され、メソッドが呼び出されると、call 命令はメソッドの ACC_SYNCHRONIZED アクセス フラグが設定されているかどうかをチェックします。設定されている場合、実行スレッドは最初に同期ロックを保持し、次にメソッドを実行し、最後にメソッドの実行後にロックを解放します。

同期されたコード ブロックは、monitorenter および monitorexit 命令を使用して同期されます。The monitorenter instruction is inserted at the beginning of the synchronization code block after compilation, and the monitorexit is inserted at the end of the method and at the exception. JVM は、各 monitorenter が対応する monitorexit とペアになっている必要があることを確認する必要があります。どのオブジェクトにもモニターが関連付けられており、モニターが保持されるとロックされます。スレッドが monitorenter 命令を実行すると、オブジェクトのモニター所有権を取得しようとします。つまり、オブジェクトのロックを取得しようとします。

 同期ロックのアップグレード

同期ロックの最適化の背景:
ロックを使用するとデータ セキュリティを実現できますが、パフォーマンスが低下します。
ロックフリーは、スレッドの並列処理に基づいてプログラムのパフォーマンスを向上させることができますが、スレッドの安全性を低下させます。同期ロック: オブジェクト ヘッダーのマーク ワード ロック フラグ ビットに従って、現在どの種類のロックに属しているかを判断します。

 ロックエスカレーションが存在するのはなぜですか?

java5 以前では、オペレーティング システム レベルでの重量ロックと重量操作である Synchronized のみが存在します。ロック競争が激しいと性能が落ちます。ユーザー モードとカーネル モードの間に遷移があるためです。

Java スレッドはオペレーティング システムのネイティブ スレッドにマップされます. スレッドをブロックまたはウェイクアップしたい場合は, オペレーティング システムが介入する必要があり, ユーザー状態とコア状態を切り替える必要があります. この切り替えは消費しますユーザー状態とカーネル状態には専用のメモリ空間、専用レジスタなどがあるため、多くのシステム リソースが必要です。ユーザー状態からカーネル状態への切り替えには、多くの変数とパラメーターをカーネルに渡す必要があり、カーネルも保護する必要があります。カーネル モードの呼び出しが終了した後もユーザー モードに戻って作業を継続するために、ユーザー状態から切り替えるときにいくつかのレジスタ値、変数など。

Java の初期バージョンのリリースでは、同期は重いロックであり、非効率的です。これは、監視ロック (montor) が、基盤となるオペレーティング システムの MutexLoCK (システム ワーカーの反発) に依存し、一時停止と再開の両方に依存して実装されているためです。スレッドを転送する必要がある 完了するためにカーネル モードに入る、Java スレッドをブロックまたはウェイクアップするには、システムの CPU 状態を切り替えることによって完了する必要があります. この状態切り替えにはプロセッサ時間がかかります. 同期コード ブロックの内容が単純すぎる場合、切り替え時間はユーザーコードよりも速い場合があります。実行時間はまだ長い」と、時間のコストが比較的高くなります。これが、初期の同期が非効率的であった理由です。ロックの取得と解放、軽量ロック、バイアス ロックが導入されました。

バイアスロック

名前が示すように、ロックにアクセスする最初のスレッドを優先します

同期コードが同じスレッドによって複数回アクセスされた場合、スレッドは 1 つしか存在しないため、その後アクセスされるとスレッドは自動的にロックを取得します. スレッドは複数回取得し、偏ったロックの説明がこの中に現れます彼女の出現は、1 つのスレッドが同期を実行する場合のみパフォーマンスを向上させるための将来のソリューションです。

  • 操作中に同期ロックにアクセスするスレッドが 1 つだけで、マルチスレッドの競合がない場合、スレッドは同期をトリガーする必要はありません.この場合、スレッドにバイアスされたロックが追加されます. スレッドが 2 回目の同期コード ブロックに到達すると、この時点でロックを保持しているスレッドが自分自身であるかどうかを判断し、そうであれば、正常に実行を続けます。以前にロックが解除されていないため、ここで再ロックする必要はありません。最初から最後までロックを使用するスレッドが 1 つだけの場合、ロックをバイアスするための追加のオーバーヘッドがほとんどないことは明らかであり、パフォーマンスは非常に高くなります。(スレッドの上下の切り替えを回避する)、つまり、リソースの競合がない場合、バイアスロックは同期ステートメントを排除し、怠惰なロックはCAS操作さえ実行しないため、プログラムのパフォーマンスが直接向上します。
  • 実行中のプロセス中に他のスレッドがロックを取得した場合、バイアスされたロックを保持しているスレッドは中断され、JVM はバイアスされたロックを削除し、ロックを標準の軽量ロックに復元します。バイアス ロックは、リソースの競合がない場合に同期プリミティブを排除することで、プログラムのパフォーマンスをさらに向上させます。2 番目のスレッドが参加すると锁竞争、バイアス ロックは軽量ロック (スピン ロック) にアップグレードされます。軽量ロックにアップグレードする場合、バイアス ロックを無効にする必要があります。これにより、STW(stop the word)バイアス ロックを無効にするときに操作が発生します。

 

実はJDK1.6以降はバイアスロックがデフォルトで有効になっているのですが、起動時間が遅くなる(4秒)ので、
プログラム起動時にすぐ起動するようにパラメータ -XX:BiasedLockingStartupDelay=0 を追加する必要があります 

バイアス ロックをオンにします。

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

バイアスされたロックを閉じます: 閉じた後、プログラムはデフォルトで -------> 軽量ロック状態に直接入ります

-XX:-UseBiasedLocking

 

軽量ロック

マルチスレッドの競合。ただし、スレッドの競合は常に最大 1 つです。つまり、過剰なロックの競合はなく、スレッドのブロックもありません。

ロック競合に参加するスレッドはありますが、ロックを獲得するまでの競合時間は極めて短いです。本質はスピンロックCAS

主な目的: マルチスレッドの競合がないことを前提として、重いロックによるオペレーティング システムのミューテックスの使用によるパフォーマンスの消費を CAS を使用して削減します. 率直に言えば、まずスピンし、失敗した場合はブロッキングをアップグレードします.

アップグレードのタイミング: バイアス ロック機能がオフになっている場合、またはバイアス ロックのマルチスレッド競合により、バイアス ロックが軽量ロックにアップグレードされる場合

スレッド A がすでにロックを取得している場合、オブジェクトのロックはスレッド A によって取得されており、ロックは現在バイアスされたロックであるため、スレッド B が再びオブジェクトのロックを取得します。スレッド B がスクランブルしている場合、オブジェクト ヘッダー マーク ワードのスレッド ID がスレッド B 自身のスレッド ID ではない (しかしスレッド A) ことを発見すると、スレッド B はロックを取得することを望んで CAS 操作を実行します。

このとき、スレッド B の動作には 2 つの状況があります。

ロックの取得に成功した場合は、Mark Word のスレッド ID を B 自身の ID に直接置き換え (A→B)、他のスレッドに再度バイアスをかけます (つまり、バイアスをかけたロックを他のスレッドに引き渡します。これは、現在のスレッドはロックを「解放」されています)、ロックはバイアスされたロック状態を維持します。A スレッドは終了し、B スレッドは上にあります。

ロックの取得に失敗した場合、バイアス ロックは軽量ロックにアップグレードされます (バイアス ロック フラグを 0 に設定し、ロック フラグを 00 に設定します)。コードを同期すると、競合するスレッド B がスピンに入り、軽量ロックの取得を待機します。

Java6以前

デフォルトで有効になっており、スピンの数はデフォルトで 10 です。または、スピン スレッドの数が CPU コアの数の半分を超えています。

Java6以降

アダプティブ スピンロックになります。これは、スピンの数が固定されていないことを意味しますが、ロックを所有するスレッドの状態、または同じロックでの 1 つのスピンの時間に従って決定されます。

スレッドが正常にスピンすると、次回のスピンの最大数が増加します。これは、JVM が、前回の成功以来、今回は成功する可能性が高いと見なしているためです。逆に、スピンがめったに成功しない場合は、CPU のアイドリングを避けるために、スピンの数が減るか、次回はスピンしないことさえあります。

重量級ロック

適用対象: ロック競合に参加しているスレッドが多数あり、競合が非常に多い。

ヘビーロックの原理

Java の同期重量ロックは、Monitor オブジェクトの開始と終了に基づいて実装されます。コンパイル時に、同期ブロックの開始位置がモニター開始命令に挿入され、モニター終了命令が終了位置に挿入されます。

スレッドがmonitor enter命令を実行すると、オブジェクトに対応するMonitorの所有権を取得しようとし、取得された場合はロックを取得し、現在のスレッドのidをMonitorの所有者に格納するため、同期ブロックを終了しない限り、ロックされた状態になります。それ以外の場合、他のスレッドはこのモニターを取得できません。

おすすめ

転載: blog.csdn.net/m0_62436868/article/details/129909296