同期についての話/同期ロックのアップグレードについての話
Markword の内部実装
同期メソッドとバイトコードのコード ブロックの実装には大きな違いがありますが、内部実装は依然としてオブジェクト ヘッダーの MarkWord に基づいています。
jdk5 より前—重量ロック
synchronized には重量ロックのみがあり、 synchronized はオブジェクト内のモニターロック(Monitor) を通じて実装されます。
ただし、モニター ロックの本質は、基礎となるオペレーティングシステムのミューテックス ロックに依存します。
さらに、スレッド間の切り替えを実装するには、オペレーティング システムをユーザー モードからカーネル モードに切り替える必要があり、これには非常にコストがかかります。
状態間の遷移には比較的長い時間がかかるため、Synchronized は非効率的です。
したがって、ミューテックス ロックによって実装される、オペレーティング システムに依存するこの種のロックは、「重量ロック」と呼ばれます。
JDK6 の開始-バイアスされたロック、軽量ロック
同期を最適化し、適応型CAS スピン、ロックの削除、ロックの拡張、偏向锁
その他轻量级锁
の最適化戦略を追加します。
ロックは、バイアス ロックから軽量ロック、さらに重量ロックにアップグレードできます。ただし、ロックのアップグレードは一方向であり、低レベルから高レベルへのみアップグレードできます。
JDK 1.6 では、バイアスされたロックと軽量ロックがデフォルトで有効になっています。バイアスされたロックは、-XX:-UseBiasedLocking を通じて無効にできます。
バイアスロック
偏ったロックの導入は、HotSpot の作成者が研究と実践を通じて次のことを発見したためです。
ほとんどの場合、ロックは複数のスレッドと競合しないだけでなく、常に同じスレッドによって複数回取得されます。スレッドがロックを取得するコスト効率を高めるために、バイアスされたロックが導入されます。
スレッドがバイアス ロックを保持した後は、同期されたコード ブロックへのその後のアクセスでロックを取得または解放する必要はありません。
バイアス ロックは、単一のスレッドがコード ブロックを実行するときに使用されるメカニズムです。マルチスレッドの同時環境にある場合(つまり、スレッド A が同期されたコード ブロックの実行を完了しておらず、スレッド B がロック アプリケーションを開始した場合)、それは間違いなく軽量ロック アプリケーション (レベル ロックまたは重量ロック)に変換されます。
「バイアス」とは、バイアスされたロックが、そのロックを適用した最初のスレッドのみが将来そのロックを使用することを前提としている (どのスレッドも再びロックを適用しない) ことを意味します。
したがって、 Mark Word CAS に所有者を記録するだけで済みます(基本的には更新も行われますが、初期値は空です)。
記録が成功すると、バイアスされたロックの取得が成功し、記録されたロックのステータスはバイアスされたロックになります。将来、現在のスレッドが所有者と等しい場合は、ゼロコストでロックを直接取得できます。
それ以外の場合は、他のスレッドが競合しており、ロックが軽量ロックに拡張されることを意味します。
軽量ロック
軽量ロックを導入する目的は、実際には、重量ロックの使用を回避することです。
CAS スピンは、スレッドのブロックとウェイクアップがオペレーティング システムのユーザー モードとカーネル モード間の切り替えに対応するため、短期間でのスレッドのブロックとウェイクアップを回避し、それによってリソースを節約し、プログラムの実行パフォーマンスを向上させます。
したがって、さまざまなロックは互いに代替するものではなく、さまざまなシナリオで異なる選択肢となります。
- バイアスされたロック: 実際の競合はなく、ロックを申請した最初のスレッドのみが今後そのロックを使用します。
- 軽量ロック: 実際の競合はなく、複数のスレッドがロックを交互に使用します。短期間のロック競合は許可されます。
- ヘビー級ロック: 実際の競争と熾烈な競争が存在します
同期ロックのアップグレード プロセスの概要: 一言で言えば、最初にスピンし、失敗した場合はブロックします。
その他必要な知識
CASスピン
// 执行一个无意义的循环,目的就是等代机会去竞争到锁
while(true){
//空的
//一个:每次自旋的时间
//另外一个:自旋的次数
}
スピンの役割:
スレッドのブロックとウェイクアップはオペレーティング システムのユーザー モードとカーネル モード間の切り替えに対応するため、短期間でのスレッドのブロックとウェイクアップが回避され、リソースが節約され、プログラムの実行パフォーマンスが向上します。
ロック情報の保管
ロック情報はオブジェクトヘッダのMarkワードに格納され、Markワードにはロック状態に応じて対応するロック情報が格納されます。
同期とロックのアップグレード:オブジェクト ヘッダーのマーク ワードが再利用され、ロック アップグレード戦略はさまざまなロック フラグに基づいています。
-
バイアスされたロック: MarkWord はバイアスされたスレッド IDを保存します。
-
軽量ロック: MarkWord はスレッド内のロック レコードへの
栈
ポインタを保存します。 -
重みロック: MarkWord はモニター オブジェクトへの
堆
ポインターを に保存します。
ロック状態
ロック解除状態
バイアスロック状態
軽量ロック状態
ヘビーウェイトロックステータス
さまざまなロックのメリットとデメリット
アリババ開発マニュアル
[必須] 同時実行性が高い場合は、同期呼び出しを考慮する必要があります锁的性能损耗
。
ロックフリーのデータ構造を使用できる場合は、ロックを使用しないでください。ブロックをロックできる場合は、メソッド本体全体をロックしないでください。オブジェクト ロックを使用できる場合は、クラス ロックを使用しないでください。
注: ロックされたコード ブロックのワークロードをできるだけ小さくし、ロックされたコード ブロック内で RPC メソッドを呼び出さないようにしてください。
面接でよくある質問
1. 同期の最適化について言及しましたが、バイアスされたロックと軽量ロックの違いについて詳しく教えていただけますか?
異なる CAS 時間、アクティブにロックを解放するかどうか
軽量ロックでは、ロックの適用と解放のたびに少なくとも 1 つの CAS が必要ですが、バイアスされたロックでは初期化中に 1 つの CAS のみが必要です。
バイアスされたロックは、最初にアクセスしたスレッドのみを優先します。このスレッド (スレッド A) が初めて同期ブロックにアクセスすると、CAS を使用してオブジェクト ヘッダーの ThreadID がスレッドバイアス ID に更新されます。後続のアクセスでは、threadId が同じかどうかを比較するだけでよく、CAS 操作は必要ありません。また、この方向のスレッドは、競合するスレッドが存在しない限り、自動的にロックされません。
2 番目のスレッド (スレッド B) が訪問したとき、彼は最初のスレッドが既に存在していることを知りません。したがって、この 2 番目のスレッドは優先されていると考え、バイアスされたスレッドと同様に CAS を使用してオブジェクト ヘッダーの ThreadID を初期化しようとしましたが、失敗したことがわかりました。
これは、オブジェクトのロックが他のスレッドによって占有され、スレッドの競合が発生したことを意味します。
最初にオブジェクト ロックを保持していたスレッドがまだ生きているかどうかを確認します。
ハングした場合は、オブジェクトをロックのない状態に変更して、新しいスレッドにリダイレクトできます。
スレッドがまだ生きている場合は、スレッドが同期されたコード ブロック内のコードを実行しているかどうかを確認し、実行している場合は、軽量ロックにアップグレードし、CAS 競合ロックを実行します。
軽量ロックの CAS は、ロックを申請するたびに実行する必要があります。
2. synchronized キーワードは通常どのように使用しますか?
(1)インスタンス メソッドの変更: 現在のオブジェクト インスタンスをロックします。
synchronized void method() {
//业务代码
}
(2)静的メソッドを変更します。つまり、現在のクラスをロックします。
synchronized void staic method() {
//业务代码
}
(3) コードブロックを修正する
synchronized(this|object|xx.class) {
//业务代码
}
シングルトン モードでの二重取得でもシングルトンが使用されます
/* 双重检验锁 */ public class Singleton{ private Singleton(){ }//构造器私有化,防止new,导致多个实例 private static volatile Singleton singleton; public static Singleton getInstance(){ //向外暴露一个静态的公共方法 getInstance //第一层检查 if(singleton == null){ //同步代码块 synchronized (Singleton.class){ //第二层检查 if(singleton == null) { singleton = new Singleton(); } } } return singleton; } }
この記事が役に立った場合は、忘れずに Yile に「いいね!」を押してください。ありがとうございます。