1。ヘビー級ロック
前の記事では、同期されたとその実装原則の使用法を紹介しました。これで、同期されたものがオブジェクト内のモニターロックを介して実装されることを知っておく必要があります。ただし、モニターロックは、基礎となるオペレーティングシステムのMutexロックに依存することにより、本質的に実装されています。オペレーティングシステムは、ユーザー状態からコア状態にスレッドを切り替える必要があります。これは非常に費用がかかり、州間の変換には比較的長い時間がかかります。これが、同期が非効率的である理由です。したがって、オペレーティングシステムMutex Lockの実装「ヘビー級ロック」の実装に依存するこのロックと呼びます。 JDKで同期するために行われたさまざまな最適化の中核は、このヘビー級ロックの使用を減らすことです。 JDK1.6の後、ロックの取得と解放によりパフォーマンスを改善することによって引き起こされるパフォーマンスの消費を減らすために、「軽量ロック」と「バイアスロック」が導入されました。
2。軽量ロック
ロック状態には、ロックフリー状態、バイアスロック、軽量ロック、ヘビー級ロックの4つのタイプがあります。ロックの競争により、ロックはバイアスロックから軽量ロックにアップグレードでき、次にヘビー級ロックをアップグレードできます(ただし、ロックのアップグレードは一方通行であり、低から高へのアップグレードのみであり、ロックの分解はありません)。 JDK 1.6では、デフォルトでバイアスロックと軽量ロックが有効になっています。また、-XX:-USEBIASEDLOCKINGでバイアスロックを無効にすることもできます。ロック状態はオブジェクトのヘッダーファイルに保存され、32ビットのJDKを例として取得します。
ロックステータス | 25ビット | 4ビット | 1ビット | 2ビット | ||
23ビット | 2ビット | 偏ったロックですか? | フラグビットをロックします | |||
軽量ロック | スタック内のレコードをロックするポインター | 00 | ||||
ヘビー級ロック | ミューテックスへのポインター(ヘビー級ロック) | 10 | ||||
GCタグ | ヌル | 11 | ||||
ポジティブロック | スレッドID | エポック | 被験者は年齢です | 1 | 01 | |
ロックレス | オブジェクトのハッシュコード | 被験者は年齢です | 0 | 01 | ||
「軽量」は、オペレーティングシステムのミューテックスを使用する従来のロックに関連しています。ただし、最初に、軽量のロックがヘビー級のロックを置き換えるために使用されないことを強調することが重要です。当初の意図は、マルチスレッド競争なしで従来のヘビー級ロックを使用することにより生成されるパフォーマンス消費を減らすことです。軽量ロックの実行プロセスを説明する前に、まず、軽量ロックに適応したシナリオが、スレッドが同期ブロックを交互に実行する場合であることを理解します。同じロックに同時にアクセスすると、軽量ロックがヘビー級ロックに拡張されます。
1.軽量ロックのロックプロセス
(1)コードが同期ブロックに入ると、同期オブジェクトのロック状態がロックフリーの場合(ロックフラグは「01」状態、バイアスロックは「0」であるかどうか」の場合、仮想マシンは最初に現在のスレッドのスタックフレームに呼ばれるロックレコードと呼ばれるスペースを作成し、ロックオブジェクトの現在のコピーを保存します。この時点で、スレッドスタックとオブジェクトヘッダーの状態を図2.1に示します。
(2)オブジェクトヘッダーのマークワードをコピーし、ロックレコードにコピーします。
(3)コピーが成功した後、仮想マシンはCAS操作を使用してオブジェクトのマークワードをポインターに更新してロックレコードに更新し、所有者のポインターをロックレコードのポインターにオブジェクトマークワードに向けます。更新が成功した場合は、ステップ(3)を実行し、それ以外の場合はステップ(4)を実行します。
(4)この更新アクションが成功した場合、スレッドにはオブジェクトのロックがあり、オブジェクトマークワードのロックフラグは「00」に設定されています。つまり、オブジェクトは軽量ロック状態にあります。この時点で、スレッドスタックの状態とオブジェクトヘッドを図2.2に示します。
(5)この更新操作が失敗した場合、仮想マシンは最初にオブジェクトのマークワードが現在のスレッドのスタックフレームを指しているかどうかを確認します。もしそうなら、それは現在のスレッドがすでにオブジェクトのロックを持っていることを意味し、その後、同期ブロックを直接入力して実行を継続することができます。それ以外の場合、複数のスレッドがロックを競合し、軽量ロックがヘビー級ロックに拡大し、ロックフラグの状態値が「10」になります。ヘビー級ロック(Mutex)へのポインターはMark Wordに保存され、ロックを待つスレッドもブロッキング状態に入ります。現在のスレッドは、スピンを使用してロックを取得しようとします。スピンは、スレッドのブロックを避け、ループを使用してロックを取得することです。
図2.1軽量ロックCAS操作の前のスタックとオブジェクトの状態
図2.2軽量ロックCAS操作後のスタックとオブジェクトの状態
2。軽量ロックのロック解除プロセス:
(1)CAS操作を通じてスレッドにコピーされた変位したマークワードオブジェクトを交換してみてください。
(2)交換が成功した場合、同期プロセス全体が完了します。
(3)交換が失敗した場合、それは他のスレッドがロックを取得しようとしたことを意味します(現時点ではロックが展開されています)、ロックの解放中に吊り下げられたスレッドを覚醒させる必要があります。
3。ポジティブロック
バイアスロックの導入は、軽量ロックの取得とリリースは複数のCAS原子指示に依存するため、マルチスレッド競争のない不必要な軽量ロック実行パスを最小限に抑えることです。 CAS原子指示)。上記のように、軽量ロックは、スレッドが同期ブロックを交互に実行するとパフォーマンスを改善するために使用されますが、バイアスロックは、1つのスレッドのみが同期ブロックを実行するとパフォーマンスをさらに向上させるために使用されます。
1。バイアスロック取得プロセス:
(1)マークワードのバイアスロックのフラグが1に設定されているかどうか、およびロックフラグが01であるかどうかにアクセス - それがバイアス可能な状態であることを確認します。
(2)偏見状態の場合、スレッドIDが現在のスレッドを指しているかどうかをテストします。もしそうな場合は、ステップ(5)を入力し、それ以外の場合はステップ(3)を入力します。
(3)スレッドIDが現在のスレッドを指していない場合、ロックはCAS操作を通じて競合されます。競争が成功した場合は、マークワードのスレッドIDを現在のスレッドIDに設定し、実行(5)を実行します。競争が失敗した場合は、(4)を実行します。
(4)CASがバイアスロックの取得に失敗した場合、それは競争があることを意味します。グローバルサフェポイントに到達すると、バイアスロックを取得するスレッドが吊り下げられます。バイアスロックは軽量ロックにアップグレードされ、SafePointでブロックされたスレッドは同期コードの実行を継続します。
(5)同期コードを実行します。
2。バイアスロックのリリース:
バイアスロックの取り消しは、上記の4番目のステップで言及されています。バイアスロックは、他のスレッドがバイアスロックのために競合しようとする場合にのみロックを放出し、スレッドはバイアスロックを積極的に放出しません。バイアスロックのキャンセルには、グローバルなセキュリティポイントを待つ必要があります(この時点でバイトコードは実行されていません)。まずバイアスロックでスレッドを一時停止し、ロックオブジェクトがロックされた状態にあるかどうかを判断し、バイアスロックを元に戻した後、ロック解除(フラグビットは「01」)または軽量ロック(フラグビットは「00」)に戻ります。
3.ヘビー級ロック、軽量ロック、バイアスロック間の変換
図2.3 3つの変換図
この写真は、主に上記のコンテンツの要約です。上記のコンテンツをよく理解している場合、写真は簡単に理解できるはずです。
4。その他の最適化
1。適応紡糸:軽量ロックを取得するプロセスから、軽量ロックの取得中にスレッドがCAS操作を実行できない場合、スピンを通じてヘビー級ロックを取得する必要があることがわかっています。問題は、スピンにCPU消費が必要であることです。ロックを取得できない場合、スレッドはスピン状態になり、CPUリソースを無駄にします。この問題を解決する最も簡単な方法は、スピンの数を指定することです。たとえば、ロックが取得されていない場合は、スピン数を10回循環させ、ブロッキング状態を入力することです。しかし、JDKはよりスマートなアプローチ、つまり適応スピンを採用しています。簡単に言えば、スレッドが成功した場合、スピンの数は次回になり、スピンが故障した場合、スピンの数が減少します。
2。ロックの粗大化:ロックの粗さの概念は理解しやすい必要があります。これは、ロックをマージし、複数回接続された操作のロックを解除し、複数の連続ロックを範囲の範囲のロックに拡張することです。例えば:
パッケージcom.paddx.test.string; public class stringbuffertest {stringbuffer stringbuffer = new StringBuffer(); public void append(){stringbuffer.append( "a"); stringbuffer.append( "b"); stringbuffer.append( "c"); }}ここでは、StringBuffer.Appendメソッドを呼び出すたびに、ロックとロック解除が必要です。仮想マシンが同じオブジェクトでの一連のロックおよびロック解除操作を検出すると、ロック操作とロック解除操作の広い範囲に融合します。つまり、ロックは最初の追加方法で実行され、ロック解除は最後の追加方法が完了した後に実行されます。
3.ロックエリミネーション:ロックエリミネーションとは、不必要なロック操作を削除することを意味します。コードエスケープ手法によれば、コードの一部とヒープ上のデータが現在のスレッドから逃げられないと判断された場合、このコードはスレッドセーフであり、ロックする必要がないと考えることができます。次のプログラムを見てください。
パッケージcom.paddx.test.concurrent; public class synchronizedtest02 {public static void main(string [] args){synchronizedtest02 test02 = new synchronizedtest02(); //(int i = 0; i <10000; i ++){i ++; } long start = system.currenttimemillis(); for(int i = 0; i <100000000; i ++){test02.append( "abc"、 "def"); } system.out.println( "time =" +(system.currenttimemillis() - start)); } public void append(string str1、string str2){stringbuffer sb = new StringBuffer(); sb.append(str1).append(str2); }}StringBufferの追加は同期メソッドですが、このプログラムのStringBufferはローカル変数に属し、メソッドから逃げません。したがって、このプロセスは実際にはスレッドセーフであり、ロックを排除できます。これが私のローカル実行の結果です:
他の要因の影響を最小限に抑えるために、ここではバイアスロック(-XX:-USEBIASEDLOCKING)が無効になっています。上記のプログラムを通じて、ロックが排除された後、パフォーマンスが大幅に改善されたことがわかります。
注:実行結果は、JDKの異なるバージョン間で異なる場合があります。ここで使用するJDKバージョンは1.6です。
5。概要
この記事では、軽量ロックやバイアスロックなど、JDKで同期した同期の最適化に焦点を当てていますが、これら2つのロックには完全に欠点がないわけではありません。たとえば、競争が激しい場合、追加のロックアップグレードプロセスがあるため、効率を改善するだけでなく、効率を低下させます。現時点では、-XX:-USEBIASEDLOCKINGを介してバイアスロックを無効にする必要があります。これらのロックの比較は次のとおりです。
ロック | アドバンテージ | 欠点 | 適用可能なシナリオ |
ポジティブロック | ロックとロック解除には追加の消費は必要ありません。また、非同期方法を実行するのと比較して、ナノ秒ギャップがあります。 | スレッド間にロック競合がある場合、それは追加のロック取り消し消費を引き起こします。 | 同期ブロックに1つのスレッドのみがアクセスするシナリオに適しています。 |
軽量ロック | 競合するスレッドはブロックされず、プログラムの応答速度が向上します。 | ロックコンペティションを取得しないスレッドがCPUを消費する場合。 | 応答時間を追求します。 同期ブロックの実行は非常に高速です。 |
ヘビー級ロック | スレッド競争はスピンを使用せず、CPUを消費しません。 | スレッドブロック、応答時間の遅い。 | スループットを追求します。 同期ブロックの実行速度は比較的長いです。 |