なぜこれについて話すのですか?AQS をまとめた後、この点について確認してみましょう。この記事は、次のような高頻度の問題から始まります。
2番目のロックはどうなりましたか?無意識にロックが使用される状況:
シンプルロックはどうなったんですか? ロック後に何が起こるかを理解するには、オブジェクトが作成された後のメモリ内のレイアウトがどのようになっているかを確認する必要があります。 オブジェクトが作成されると、メモリ内で主に 4 つの部分に分割されます。
これら4つの部分がわかったら、最下層を確認してみましょう。サードパーティ パッケージ JOL = Java Object Layout を使用して、Java メモリ レイアウトを確認します。わずか数行のコードでメモリ レイアウト スタイルを確認できます。
結果を印刷します: 出力結果から: 1) オブジェクト ヘッダーには 3 行に分割された 12 バイトが含まれており、最初の 2 行は実際にはマークワードであり、3 行目はクラス ポインターです。ロックの前後で出力が 001 から 000 に変わることに注目してください。マークワードの使用法: 8 バイト (64 ビット) のヘッダーにはいくつかの情報が記録され、ロックによってマークワードの内容が変更されます。 8 バイト (64 ビット) のヘッダーにはいくつかの情報が記録され、ロックによってマークワードの内容が変更されます。 8 バイト (64 ビット) のヘッダーにはいくつかの情報が記録されます。 001ロック解除状態から00軽量ロック状態に変更しました。 2) 16 バイトを占める新しいオブジェクトを作成します。オブジェクト ヘッダーは 12 バイトを占めます。オブジェクトには追加の変数がないため、instance = 0 です。オブジェクトのメモリ サイズは 8 バイトで割り切れる必要があることを考慮すると、padding = 4 となり、最終的に new Object() のメモリ サイズは 16 バイトになります。 拡張: どのようなオブジェクトが旧世代に入りますか?多くのシナリオでは、たとえば、オブジェクトが大きすぎる場合は直接入ることがありますが、ここでは、Young GC のオブジェクトが最大 15 回の Young GC (年齢は調整可能、デフォルトは 15) を生き延びた場合に、なぜ Old 領域に入るのかについて説明します。上図では、ホットスポットのマークワードで世代年齢を表すために 4 ビットが使用されているため、表現できる最大範囲は 0 ~ 15 です。そのため、新しい世代の年齢は 15 を超えることはできません。職場で -XX:MaxTenuringThreshold を使用して調整できますが、通常は変更しません。 3つのロックのアップグレードプロセス1 ロックアップグレードの検証ロックのアップグレードについて説明する前に、実験をしてみましょう。コードは 2 つあり、違いは、1 つは途中で 5 秒間スリープさせ、もう 1 つはスリープさせないことです。それが何か違いをもたらすかどうか見てみましょう。
これら 2 つのコードには違いがありますか?実行して結果を確認します。 興味深いのは、メインスレッドを 5 秒間スリープさせた後のメモリレイアウト出力が、メインスレッドをスリープさせない場合の出力結果と異なることです。 Syn ロックがアップグレードされると、jdk1.8 バージョンの基盤となるデフォルト設定では、バイアス ロックが 4 秒後に有効になります。これは、バイアスロックが 4 秒以内にアクティブ化されず、ロックが追加されると、軽量ロックに直接アップグレードされることを意味します。 それで、いくつか質問があります。
質問 1: ロックをアップグレードする必要があるのはなぜですか?ロックされている場合は、ロックされています。もう一度ロックする必要はないでしょうか? まず、初期の JDK 1.2 の効率が非常に低いことは明らかです。当時、シンはヘビー級のロックでした。ロックを申請するには、オペレーティング システムのボスであるカーネルを介してシステム コールを実行し、ソート操作のキューに入り、操作が完了したらユーザー状態に戻る必要があります。 カーネル状態: ユーザー状態がハードウェアに直接アクセスする危険な操作を実行したい場合、ハードウェアを強制終了するのは簡単です (フォーマット、ネットワーク カードへのアクセス、メモリへのアクセスなど)。システム セキュリティのため、オペレーティング システムはユーザー状態とカーネル状態の 2 つの層に分かれています。ロック リソースを適用する場合、ユーザー状態をオペレーティング システムのカーネル状態に適用する必要があります。 Jdk1.2 では、ユーザーはカーネル状態からロックを申請する必要があり、その後カーネル状態がそれをユーザー状態に渡します。このプロセスは非常に時間がかかり、初期段階では効率が極めて低くなります。 JVM で処理できる一部のタスクをオペレーティング システムに任せるのはなぜでしょうか?効率を向上させるために、JVM で完了できるロック操作を抽出できますか?そのため、ロックの最適化が提案されます。 質問 2: バイアス ロックはなぜ必要なのでしょうか? 実際、これは本質的に確率の問題です。統計によると、日常的に使用する syn lock プロセスの 70% ~ 80% のケースでは、ロックを取得するスレッドは通常 1 つだけです。例えば、よく使う System.out.println や StringBuffer は最下層に syn ロックがありますが、基本的にマルチスレッド競合はありません。この場合、軽量ロック レベルにアップグレードする必要はありません。バイアスの重要性は、最初のスレッドがロックを取得し、そのロックに独自のスレッド情報をマークするため、次回ロックを取得するときに検証のためにロックを取得する必要がないことです。複数のスレッドがロックを取得しようとすると、バイアス ロックは取り消され、軽量ロックにアップグレードされます。実際、厳密に言えば、バイアス ロックは実際のロックではないと思います。バイアス ロックは、共有リソースにアクセスしようとするスレッドが 1 つだけである場合にのみ発生するためです。 ロックが意図せず使用されるシナリオ:
質問 3: JDK8 ではなぜ 4 秒後にバイアス ロックを有効にする必要があるのですか? 実際のところ、これは妥協です。コードが最初に実行されるときには、ロックを取得しようとするスレッドが多数あるはずです。バイアスロックをオンにすると、効率が低下します。したがって、上記のプログラムが 5 秒間スリープした後、バイアス ロックがオンになります。バイアスロックを追加すると効率が低下するのはなぜですか?途中にいくつかの余分なプロセスがあるからです。バイアス ロックが設定された後、複数のスレッドが共有リソースを競合する場合、ロックを軽量ロックにアップグレードする必要があります。このプロセスでは、アップグレード前にバイアス ロックを取り消す必要があるため、効率が低下します。なぜ4sなのですか?これは統計的な時間値です。 もちろん、パラメータ -XX:-UseBiasedLocking = false を設定することで、バイアス ロックを無効にすることもできます。 jdk15 以降では、バイアス ロックはデフォルトで無効になっています。この記事では、jdk8 環境でのロックのアップグレードを検証します。 2 ロックのアップグレードプロセス上記では、オブジェクトが作成後、ロックフリー状態→バイアスロック(有効な場合)→軽量ロックの順にメモリに入るプロセスを検証しました。ロックのアップグレードプロセスが進むにつれて、軽量ロックは重量級ロックになります。まず、軽量ロックとは何かを理解する必要があります。 1 つのスレッドがリソースを取得する (バイアス ロック) 状態から、複数のスレッドがリソースを取得して軽量ロックにアップグレードする状態まで、スレッドの数がそれほど多くない場合、これは実際には CAS (Compare and Swap と呼ばれる、値の比較と交換) として理解できます。並行プログラミングの最も単純な例は、concurrent パッケージのアトミック操作クラス AtomicInteger です。 ++ のような操作を実行する場合、基礎となるレイヤーは実際には CAS ロックです。
質問 4: どのような状況で軽量ロックを重量ロックにアップグレードする必要がありますか? まず考えられるのは、スレッドが複数ある場合は、まず軽量ロックを解除し、それが通せない場合にのみ重量ロックにアップグレードすることです。では、どのような状況では軽量ロックではその役割を果たせないのでしょうか? 1. スレッドが多すぎる場合 (たとえば、最初に 10,000 個)、CAS が値を交換するのにどのくらいの時間がかかりますか?同時に、CPU はこれらの 10,000 個のアクティブ スレッドを切り替えるだけで膨大なリソースを消費します。この場合、当然、重量級のロックにアップグレードされ、キュー管理のためにオペレーティング システムに直接呼び出されます。この場合、たとえ 10,000 個のスレッドがあったとしても、ウェイクアップのためにキューイングされるのを待っている休止状態も処理することになります。 2. CAS が 10 回スピンしてもロックを取得できない場合は、ヘビー級にアップグレードされます。 一般的に、2 つの状況は軽量から重量にアップグレードされます。 10 回スピンしているスレッドや CPU スケジューリングを待機しているスレッドの数が CPU コア数の半分を超えると、自動的に重量ロックにアップグレードされます。サーバーの CPU のコア数を確認するには、top コマンドを入力して 1 を押します。 質問 5: 誰もが syn はヘビー級のロックだと言いますが、その重要性は何ですか? JVM は遅延型であり、スレッド関連の操作はすべてオペレーティング システムに任せます。たとえば、スケジュール ロックの同期は、実行のためにオペレーティング システムに直接引き渡されます。オペレーティング システムでは、実行するには、まずキューに入る必要があります。さらに、スレッドを開始するときにオペレーティング システムが大量のリソースを消費する必要があり、リソースの消費が比較的多くなります。これが重い点です。 ロックのアップグレードプロセス全体を図に示します。 同期の4つの基本実装 上記のオブジェクトのメモリ レイアウトをある程度理解すると、ロック ステータスは主にマークワードに格納されることがわかります。ここでは、基礎となる実装について見ていきます。
この単純なコードを分解して何が起こるか見てみましょう。 javap -c RnEnterLockDemo.class まず、syn にはロック操作が必要であることがわかります。表示される情報には、monitorenter と monitorexit が表示されます。これらはロックとロック解除に関する指示であると主観的に推測できます。興味深いのは、1 monitorenter と 2 monitorexit です。なぜ?通常、ロックとリリースロックが 1 つずつあるはずです。実際、syn と lock の違いもここに反映されています。 syn は JVM レベルのロックです。例外が発生した場合、自分で解放する必要はありません。JVM が自動的に例外の解放を支援します。この手順は追加の monitorexit に依存します。ロック例外は手動でキャプチャして解放する必要があります。 これら 2 つの命令の機能については、JVM 仕様の説明を直接参照します。 モニター入力: 各オブジェクトはモニターに関連付けられています。モニターは、所有者がいる場合にのみロックされます。 monitorenter を実行するスレッドは、次のように、objectref に関連付けられたモニターの所有権を取得しようとします。? objectref に関連付けられたモニターのエントリ数が 0 の場合、スレッドはモニターに入り、そのエントリ数を 1 に設定します。スレッドが既にオブジェクト参照に関連付けられたモニターを所有している場合、スレッドはモニターのエントリ数がゼロになるまでブロックし、その後所有権の取得を再度試みます。 翻訳する: 各オブジェクトにはモニター ロックがあります。モニターが使用されている場合、モニターはロックされた状態になります。スレッドが monitorenter 命令を実行すると、モニターの所有権を取得しようとします。プロセスは次のとおりです。
モニター終了: monitorexit を実行するスレッドは、objectref によって参照されるインスタンスに関連付けられたモニターの所有者である必要があります。 翻訳する: monitorexit を実行するスレッドは、objectref に対応するモニターの所有者である必要があります。命令が実行されると、モニターのエントリ番号が 1 減少します。エントリ番号が 1 減少した後に 0 になった場合、スレッドはモニターを終了し、このモニターの所有者ではなくなります。このモニターによってブロックされた他のスレッドは、このモニターの所有権を取得しようとする可能性があります。 この段落の説明を通じて、Synchronized の実装原理を明確に理解することができます。同期は最下層のモニターオブジェクトを通じて完了します。 wait/notify などのメソッドは、実際にはモニター オブジェクトに依存します。このため、wait/notify などのメソッドは、同期されたブロックまたはメソッド内でのみ呼び出すことができ、それ以外の場合は java.lang.IllegalMonitorStateException がスローされます。 各ロック オブジェクトには、ロック カウンターと、ロックを保持しているスレッドへのポインターがあります。 monitorenter を実行したときに、対象オブジェクトのカウンターがゼロの場合は、他のスレッドによって保持されていないことを意味します。 Java 仮想マシンは、ロック オブジェクトの保持スレッドを現在のスレッドとして設定し、そのカウンターを i だけ増加します。ターゲット ロック オブジェクトのカウンターがゼロでない場合、ロック オブジェクトの保持スレッドが現在のスレッドであれば、Java 仮想マシンはカウンターに 1 を追加できます。それ以外の場合は、保持スレッドがロックを解放するまで待機する必要があります。 monitorexit が実行されると、Java 仮想マシンはロック オブジェクトのカウンターを 1 減らす必要があります。カウンターが 0 の場合、ロックが解放されたことを示します。 要約するこれまでの経験上、シンクロナイズドを使えば、重量級のロックになると考えられます。これは JDK 1.2 より前では当てはまりましたが、後に重すぎてオペレーティング システムのリソースを大量に消費することが判明したため、同期が最適化されました。将来的にはそのまま使用できます。ロックの強度に関しては、基盤となる JVM がすでにそれを実行しているので、それを直接使用することができます。 最後に、最初の質問を見て、すべて理解できたかどうかを確認します。疑問を念頭に置いて調査すると、物事がより明確になることがよくあります。これが皆さんのお役に立てば幸いです。 |
<<: ガートナー:Amazon、Microsoft、Alibabaが2020年の世界パブリッククラウドサービスのトップ3にランクイン
>>: デジタル産業を支援し、インテリジェントな未来をつなぐ――西安航空基地企業「ファーウェイ参入」デジタル変革社長クラス
筆者も80年代以降の人間ですが、今の若者は物事に対して衝動的になっていると感じています。実は、これは...
私は「卸売トラフィックの達人」というタイトルの記事を書き、Qunar、Meilishuo、Rong3...
計測機器は高価な工業製品であり、その価格は数千元から数万元にもなります。実験室や精密機器、設備の価格...
[[259132]] 2019 年第 1 四半期には、世界のクラウド業界で将来を決定づける大きな出来...
今日は、Web サイトを注意深く構築する方法についてお話ししたいと思います。まず、私自身の個人的な経...
「Calm Black Tea」の最近の人気により、「Calm」という言葉がさまざまな製品や名詞に登...
Baidu が盲目的に Google を模倣しているとき、Google 検索が常に改善されていること...
プライベートクラウドとは何ですか?まず、プライベート クラウドとは何かを簡単に見てみましょう。プライ...
edgenat も今年の Double Eleven に参加しました。すべての VPS が年間支払い...
上海の記者タオ・リー先日の中国経済人オブ・ザ・イヤー授賞式で、王建林氏が「近いうちにジャック・マー氏...
最適化の仕事に携わってきたこの数年間、私はあまりにも多くの業界と接してきました。その中でも、医療業界...
「クラウドを総合的かつ徹底的に活用する時代が到来します。」 2022年の天猫双十一技術共有会で、アリ...
buyvirt のブラックフライデー香港 VPS について話しましょう。他のコンピュータ ルームを見...
百度の入札力はこんなにも強いのか?SEOはどこへ向かうべきなのか?中国の現状から判断すると、百度は約...
QQマーケティングについては、これまで何度も皆さんと共有してきました。しかし、グループ内の一部の友人...