ホームページ  >  記事  >  类库下载  >  Java ロックの最適化

Java ロックの最適化

高洛峰
高洛峰オリジナル
2016-10-15 13:55:551559ブラウズ

1. 同期の原則

JVM 仕様では、JVM が Monitor オブジェクトへの出入りに基づいてメソッド同期とコード ブロック同期を実装することが規定されていますが、この 2 つの実装の詳細は異なります。コード ブロックの同期は、monitorenter 命令とmonitorexit 命令を使用して実装され、メソッドの同期は別のメソッドを使用して実装されます。詳細は JVM 仕様に指定されていませんが、これら 2 つの命令を使用してメソッドの同期を実現することもできます。 Monitorenter 命令はコンパイル後に同期されたコード ブロックの先頭に挿入されますが、monitorexit はメソッドと例外の最後に挿入されます。JVM は、各 Monitorenter が対応する Monitorexit とペアになっていることを確認する必要があります。どのオブジェクトにもモニターが関連付けられています。モニターを保持すると、そのオブジェクトはロック状態になります。スレッドがmonitorenter命令を実行すると、オブジェクトに対応するモニターの所有権を取得しようとします。つまり、オブジェクトのロックを取得しようとします。

2. Java オブジェクト ヘッダー

は Java オブジェクト ヘッダー内でロックされています。オブジェクトが配列タイプの場合、仮想マシンは 3 ワード (ワード幅) を使用してオブジェクト ヘッダーを保存します。オブジェクトが非配列タイプの場合、仮想マシンは 2 ワード (ワード幅) を使用してオブジェクト ヘッダーを保存します。 32 ビットの仮想マシンでは、1 ワード幅は 4 バイト、つまり 32 ビットに相当します。

Java ロックの最適化

Java オブジェクト ヘッダーのマーク ワードには、デフォルトでオブジェクトのハッシュコード、世代年齢、ロック マーク ビットが格納されます。 32 ビット JVM の Mark Word のデフォルトのストレージ構造は次のとおりです:

Java ロックの最適化

動作中、ロック フラグの変化に応じて Mark Word に格納されるデータも変化します。 Mark Word は、次の 4 種類のデータを保存するように変更される可能性があります:

Java ロックの最適化

3. いくつかの種類のロック

スレッドのブロックとウェイクアップでは、CPU がユーザー モードからコア モードに頻繁に切り替える必要があります。 CPU に有害です。非常に負担のかかる作業です。

ロックの取得と解放によるパフォーマンスの消費を削減するために、Java SE1.6 では「バイアスロック」と「軽量ロック」が導入されました。そのため、Java SE1.6 には、ロックなしステータス、バイアスロックステータスの 4 つのロック状態があります。 、軽量ロック ステータスと重量ロック ステータスは、競技状況に応じて徐々にエスカレートします。

ロックはアップグレードできますが、ダウングレードはできません。つまり、バイアスされたロックを軽量ロックにアップグレードした後は、バイアスされたロックにダウングレードすることはできません。ダウングレードではなくロックのアップグレードを行うこの戦略の目的は、ロックの取得と解放の効率を向上させることです。

3.1 偏ったロック

Hotspot の作成者は、これまでの研究を通じて、ほとんどの場合、マルチスレッドでロックの競合が存在しないだけでなく、常に同じスレッドによって複数回ロックが取得されることを発見しました。バイアスされたロックの目的は、スレッドがロックを取得した後のロック再入 (CAS) のオーバーヘッドを排除することであり、これによりスレッドが保護されるように見えます。

Java ロックの最適化

バイアスされたロックについてさらに理解する

バイアスされたロックを解放するために何もする必要はありません。つまり、バイアスされたロックに追加されたMarkValueは常にバイアスされたロックの状態を保持します。同じスレッドが追加を続けた場合でも、ロックのロックを解除するためのオーバーヘッドはありません。

一方、バイアスされたロックは、ロックの競合が発生すると軽量ロックよりも終了される可能性が高くなりますが、一般的なバイアスされたロックは、異なるスレッドがロックを申請した場合に軽量ロックにアップグレードされます。オブジェクトが最初にスレッド 1 によってロックおよびロック解除され、次にスレッド 2 によってロックおよびロック解除され、プロセス内にロックの競合がない場合でも、偏ったロックの失敗が発生するという違いは、今度はロックに縮退することです。図に示すように、-free first. ステータスに加えて軽量ロックも追加されています。

Java ロックの最適化

さらに、JVM は複数のスレッドがロックされている状況も最適化しましたが、ロックの競合はありません。しかし、実際のアプリケーションでは、相互排他に加えて、スレッドが以前に同期関係を持っている可能性があり、同期された 2 つのスレッド (前に 1 つと後ろに 1 つ) の間で共有オブジェクト ロックの競合が発生する可能性が高いため、この状況は実際に起こり得ます。矛盾はありません。この場合、JVM はロックバイアスされたタイムスタンプを表すためにエポックを使用します (実際にタイムスタンプを生成するには非常にコストがかかるため、エポックについては、これが公式の説明 A です)。バルク リバイアスと呼ばれる同様のメカニズムは、クラスのオブジェクトが異なるスレッドによってロックおよびロック解除されるが同時には行われない状況を最適化します。これは、クラス内のエポック値がバイアス ロックを無効にすることなく、クラスのすべてのインスタンスのバイアスを無効にします。バイアスの有効性を示すタイムスタンプ。この値は、オブジェクト割り当て時にヘッダー ワードにコピーされ、次回このクラスのインスタンスが実行されるときに、一括再バイアスを効率的に実装できます。ロックされると、コードはヘッダー ワード内の異なる値を検出し、現在のスレッドに向けてオブジェクトを再バイアスします。ロック バイアスのスレッド ID は、スタック フレームのロック レコードに保存されます。同期ブロックに出入りするときにロックとロック解除に CAS 操作を費やしますが、オブジェクト ヘッダーにマーク ワードが現在のスレッドを指すバイアス ロックを格納しているかどうかをテストするだけで済みます。テストが成功した場合、それはスレッドが持っていることを意味します。テストが失敗した場合は、Mark Word のバイアス ロック フラグが 1 に設定されているかどうか (現在バイアス ロックであることを示します) を再度テストする必要があります。設定されていない場合は、CAS を使用してロックを競合します。設定されている場合は、CAS を使用して、オブジェクト ヘッダーのバイアスされたロックを現在のスレッドに向けようとします。

偏ったロックの取り消し

偏ったロックは、競合が発生するまで待ってからロックを解放するメカニズムを使用しているため、他のスレッドが偏ったロックをめぐって競合しようとすると、偏ったロックを保持しているスレッドがロックを解放します。バイアスされたロックを取り消すには、グローバル セーフティ ポイントを待つ必要があります (この時点ではバイトコードは実行されていません)。まずバイアスされたロックを保持しているスレッドを一時停止し、次にバイアスされたロックを保持しているスレッドが生きているかどうかを確認します。スレッドがアクティブな状態ではない場合、オブジェクト ヘッダーをロックなしの状態に設定します。スレッドがまだ生きている場合は、バイアスされたロックを持つスタックが実行され、バイアスされたオブジェクトのロック レコードが走査されます。スタック内のレコードと、オブジェクト ヘッダーのマーク ワードが再び他のスレッドにバイアスされるか、ロックフリーに戻るか、オブジェクトがバイアスされたロックとして不適切であるとマークされ、最後に一時停止されたスレッドがウェイクアップされます。以下の図のスレッド 1 はバイアス ロックの初期化のプロセスを示し、スレッド 2 はバイアス ロックのキャンセルのプロセスを示します。

バイアス ロックの設定

バイアス ロックをオフにする: Java 6 および Java 7 ではバイアス ロックがデフォルトで有効になっていますが、アプリケーションの起動後数秒が経過するまではアクティブになりません。必要に応じて、JVM パラメーターを使用してオンにできます。遅延をオフにする - XX: BiasedLockingStartupDelay = 0。アプリケーション内のすべてのロックが通常、競合状態にあることが確実な場合は、JVM パラメーター -XX:-UseBiasedLocking=false を使用してバイアスされたロックをオフにできます。そうすれば、デフォルトで軽量ロック状態になります。

3.2 スピンロック

スレッドのブロックとウェイクアップには、CPU がユーザー モードからコア モードに移行する必要があります。頻繁にブロックとウェイクアップを行うと、CPU に大きな負担がかかります。同時に、整数の自己インクリメント操作など、多くのオブジェクト ロックのロック状態は短期間しか持続しないことがわかります。スレッドをブロックしてウェイクアップすることは明らかに価値がありません。短期間だったため、スピンロックが導入されました。

いわゆる「スピン」とは、スレッドに無意味なループを実行させ、ループ終了後に再度ロックを競合させてループを継続させるもので、スレッドは常に実行状態になります。ただし、JVM ベースのスレッドのスケジューリングはタイム スライスを転送するため、他のスレッドはロックを適用したり解放したりする機会がまだあります。

スピン ロックは、ブロッキング ロックの時間とスペース (キューのメンテナンスなど) のオーバーヘッドを節約しますが、長期的なスピンは「ビジー待機」になり、ビジー待機は明らかにブロック ロックよりも悪いです。したがって、スピン数は通常、10、100 などの範囲内で制御されます。この範囲を超えると、スピン ロックはブロッキング ロックにアップグレードされます。

スピン ロック期間の選択に関して、HotSpot はスレッド コンテキストの切り替え時が最適であると考えていますが、これまでのところ行われていません。調査の結果、現在、HotSpot はアセンブリを通じて数個の CPU サイクルを一時停止しているだけです。スピン サイクルの選択に加えて、次のような他の多くのスピン最適化戦略も実行されます。

平均負荷が CPU よりも低い場合、スピンし続けます。

(CPU/2) を超えるスレッドが回転している場合、後続のスレッドは直接ブロックされます

回転しているスレッドが所有者が変更されたことを検出すると、スピン時間 (スピン数) が遅延するか、ブロッキングに入ります。 CPU が省電力モードの場合、スピンは停止します

スピン時間の最悪のシナリオは、CPU のストレージ遅延 (データを保存する CPU A とデータを学習する CPU B の間の時間差) です

3.3軽量ロック

軽量ロック ロック

スレッドが同期ブロックを実行する前に、JVM はまず現在のスレッドのスタック フレームにロック レコードを保存するためのスペースを作成し、オブジェクト ヘッダーのマーク ワードをロックにコピーします。これは正式には Displaced Mark Word と呼ばれます。次に、スレッドは CAS を使用して、オブジェクト ヘッダーのマーク ワードをロック レコードへのポインターに置き換えようとします。成功した場合は、現在のスレッドがロックを取得します。失敗した場合は、スピンの取得がまだ失敗している場合は、他のスレッドがロックを取得していることを意味します (同じロックを取得しているスレッドが 2 つ以上ある)。その後、軽量のレベル ロックが重量ロックに拡張されます。

軽量ロックのロック解除

軽量のロック解除では、アトミック CAS 操作が使用されて、Displaced Mark Word がオブジェクト ヘッダーに置き換えられます。成功した場合は、同期プロセスが完了したことを意味します。失敗した場合は、他のスレッドがロックを取得しようとしたことを意味するため、ロックを解放するときに一時停止されたスレッドを起動する必要があります。以下の図は、2 つのスレッドが同時にロックを競合し、ロックの拡張を引き起こすフローチャートです。

Java ロックの最適化

Java ロックの最適化

3.4 ヘビーウェイトロック

ウェイトロックは、JVM ではオブジェクトモニター (Monitor) とも呼ばれ、C の Mutex によく似ています。Mutex 相互排他機能を備えていることに加えて、セマフォ機能の実装を担当します。つまり、競合するロック用のキューと信号をブロックするキュー (待機キュー) が少なくとも含まれます。前者は相互排他を担当し、後者はスレッドの同期に使用されます。

4. ロックの長所と短所の比較

Java ロックの最適化

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。