目次
1.同期の特徴
(1) 相互排除
synchronized は、相互排除によって原子性を実現します (スレッド セーフの 4 つの主要な特性の 1 つ)。
Synchronized には相互排除効果があります。あるスレッドが特定のオブジェクトの Synchronized に対して実行されると、他のスレッドも同じ Synchronized オブジェクトに対して実行されると、ブロックされて待機します。同時に、ロックを所有してコードを実行できるスレッドは 1 つだけです。
1. 同期によって変更されたコード ブロックを入力します。これは、ロックに相当します。
2. 同期によって変更されたコード ブロックを終了します。これはロック解除と同じです。
synchronized void increase(){//メソッドを入力します。これは、
現在のオブジェクトの count++ と同等です。}//実行の完了は、現在のオブジェクトを「ロック解除」することと同じです
同期によって使用されるロックは、Java オブジェクト ヘッダーに格納されます。同期の最下層は、オペレーティング システムのミューテックス ロックを使用して実装されます。
各オブジェクトがメモリに格納されると、現在の「ロックされた」状態を表すメモリの断片が存在することが大まかに理解できます。現在「ロック解除」状態であれば使用可能で、使用時は「ロック」状態にする必要があります。現在「施錠」状態の場合、他の方はご利用いただけず、順番待ちのみとなります。最初にスレッドがロックされ、他のスレッドはスレッドが解放されるのを待つことしかできません。
注意点:
ロックごとに、待機キューがオペレーティング システム内に維持されます。このロックが特定のスレッドによって占有されている場合、他のスレッドがそれをロックしようとしますが、追加することはできず、ブロックされ、前のスレッドのロックが解除されるまで待機し、オペレーティング システムが新しいスレッドを起動してから、これを取得します。ロック、ロック。
前のスレッドがロック解除された後、次のスレッドはすぐにロックを取得できません。代わりに、「ウェイクアップ」するのはオペレーティングシステム次第です。これは、オペレーティング システムのスレッド スケジューリングの作業の一部でもあります。
3 つのスレッド ABC があり、スレッド A が最初にロックを取得し、次に B がロックを取得しようとし、次に C が再度ロックを取得しようとすると、この時点で B と C の両方がブロッキング キューで待機しているとします。しかし、A がロックを解放すると、B は C より先に来ますが、B はロックを取得できず、再び C と競合し、先着順の規則に従いません。
(2) 記憶をリフレッシュする
Synchronized は、ロックを追加してロックを削減することにより、メモリの可視性を保証できます。
同期の作業プロセス:
1. ミューテックスを取得する
2. 変数の最新のコピーをメイン メモリから作業メモリにコピーする
3. コードを実行する
4. 共有変数の変更された値をメイン メモリに更新する
5. 解放するこのように同期されたミューテックスは
、メモリの可視性も保証します。
(3) リエントラント
synchronized は特定の制約のみを持つことができ、命令の並べ替えを完全に禁止することはできません。同期された同期ブロックは、同じスレッドに対して再入可能であり、それ自体をロックする問題はありません。
// 最初のロック、ロックの成功
lock();
// 2 回目のロック、ロックはすでに占有されており、ブロックして待機しています
。
それ自体をロックする場合、スレッドがロックを解放せず、再度ロックを試みることを意味します。
ロックの以前の設定に従って、ロックが 2 回目に追加されたときにブロックして待機します。最初のロックが解放されるまで、2 番目のロックを取得することはできません。最初のロックの解放もこのスレッドによって行われるため、このスレッドはブロックされて待機し、ロック解除操作を実行できません。このとき、デッドロックします。このようなロックは、再入不可ロックと呼ばれます。
Java の同期はリエントラント ロックなので、上記の問題は発生しません。リエントラント ロックの内部には、「スレッド ホルダー」と「カウンター」という 2 つの情報が含まれています。スレッドがロックされているときにロックが誰かによって占有されていることをスレッドが検出し、その占有者がたまたま自分自身である場合、スレッドは引き続きロックを取得し、カウンターをインクリメントすることができます。ロックを解除すると、実際にはカウンターが 0 になるとロックが解放され、この時点では他のスレッドだけがロックを取得できます。
第二に、同期の使用
(1) 共通メソッドの修正
ロックされた SynchronizedDemo オブジェクト
public class SynchronizedDemo {
public synchronized void methond() {
}
}
(2) 変更された静的メソッド
ロックする SynchronizedDemo クラスのオブジェクト
public class SynchronizedDemo {
public synchronized static void method() {
}
}
(3) 修正コードブロック
ロックのオブジェクトを明示的に指定する
現在のオブジェクトをロックする
public class SynchronizedDemo {
public void method() {
synchronized (this) {
}
}
}
ロックオブジェクト
public class SynchronizedDemo {
public void method() {
synchronized (SynchronizedDemo.class) {
}
}
}
2 つのスレッドが同じロックを求めて競合し、ブロッキング待機が発生することに注意してください。2 つのスレッドがそれぞれ 2 つの異なるロックを取得しようとしますが、競合は発生しません。
スリー、シンクロロック機構
(1) 基本機能
JDK 1.8、ロック作業プロセスのみを考慮してください。JVM は、同期ロックをロックフリー、バイアス ロック、軽量ロック、および重量ロック状態に分割します。状況に応じて順次アップしていきます。
1. 最初は楽観的ロック. ロック競合が多発すると悲観的ロックに変換. 2.
最初は軽量ロック実装. ロックが長時間保持される場合3.軽量の実装 マグニチュード ロックに
最も使用される可能性が高いスピン ロック戦略
4. 不公平なロックです
5. リエントラント ロックです
6. 読み取りではありません-書き込みロック
(2)施錠作業工程
1.バイアスロック
ロックを試みる最初のスレッドは、最初にバイアス ロック状態に入ります。
偏ったロックは実際には「ロック」されているわけではありませんが、オブジェクト ヘッダーに「偏ったロック マーク」を作成して、ロックが属するスレッドを記録します。今後ロックを求めて競合するスレッドが他にない場合は、他の同期操作は必要なく、ロックとロック解除のオーバーヘッドが回避されます。他のスレッドが今後ロックを争う場合、現在のロックが属するスレッドがロックオブジェクトに記録されているため、現在ロックを申請しているスレッドが以前に記録されたスレッドであるかどうかを簡単に識別できます。その後、元の偏ったロック状態をキャンセルし、一般的な軽量ロック状態に入ります。偏ったロックは、本質的に「遅延ロック」と同等です。ロックせずにロックできる場合は、不必要なロックのオーバーヘッドを避けるようにしてください。しかし、行うべきマークはまだ行う必要があります。そうしないと、実際のロックがいつ必要なのかを区別することができなくなります。
偏ったロックとは何ですか? インタビューでよく尋ねられます。偏ったロックは実際にはロックされませんが、ロックのオブジェクト ヘッダーにマークを記録し、ロックが属するスレッドを記録します。他のスレッドが競合するロックに参加していない場合、ロック操作は実際には実行されないため、プログラムのオーバーヘッドが削減されます。他のスレッドの競合が実際に発生したら、偏ったロック状態を解除して軽量ロック状態にします。
2.軽量ロック
他のスレッドが競合すると、偏ったロック状態が解消され、軽量ロック状態 (適応スピン ロック) になります。
ここでの軽量ロックは、CAS によって実現されています。
CAS を使用して、メモリ ブロック (null => スレッド参照など) を確認および更新します。更新が成功すると、ロックは成功したと見なされます。更新が失敗した場合、ロックが占有されていると見なされ、スピンのような待機が (CPU を放棄することなく) 続行されます。
スピン操作は、CPU を常にアイドル状態に保ち、CPU リソースを浪費します。そのため、ここでの回転は永遠に続くわけではなく、一定時間、リトライ回数を重ねると回転が止まります。「アダプティブ」とも呼ばれる
3.重量級ロック
競争が激しくなると、スピンがすぐにロック状態を獲得できなくなり、重量級のロックへと展開していきます。ここでいう重いロックとは、カーネルが提供するミューテックスを指します。
ロック操作を実行するには、まずカーネル状態に入ります。現在のロックがカーネル モードで既に占有されているかどうかを確認します。ロックが占有されていない場合は、ロックが正常に取得され、ユーザー モードに戻ります。ロックが占有されている場合、ロックは失敗します。この時点で、スレッドはロックの待機キューに入り、ハングします。オペレーティング システムによって起動されるのを待機しています。一連の操作の後、他のスレッドによってロックが解除され、オペレーティング システムも中断されたスレッドを記憶していたので、それが起きました。このスレッドは、ロックの再取得を試みます。
(3) 運用の最適化
1. ロック解除
ロックを解除できるかどうかはコンパイラ+JVMが判断します。可能であれば削除してください。
ロックの排除: Synchronized は一部のアプリケーションのコードで使用されていますが、マルチスレッド環境では使用されていません。(例: StringBuffer) この時点で、各追加呼び出しにはロックとロック解除が含まれます。しかし、このコードが 1 つのスレッドでのみ実行される場合、これらのロック操作とロック解除操作は不要であり、一部のリソース オーバーヘッドが無駄に浪費されます。消去可能です。
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");
2.ロック粗大化
ロジックの一部に複数のロックとロック解除がある場合、コンパイラ + JVM は自動的にロックを粗くします。
ロックの粒度: 粗いおよび細かい
実際の開発プロセスでは、細粒度のロックを使用すると、ロックが解放されたときに他のスレッドがロックを使用できるようになることが期待されます。しかし、このロックを取得する他のスレッドが実際には存在しない可能性があります。この場合、JVM は自動的にロックを粗くして、頻繁にロックを適用したり解放したりしないようにします。たとえば、部下に仕事のタスクを説明する: 方法 1: 電話をかけて、タスク 1 を説明し、電話を切る。電話をかけ、タスク 2 を与え、電話を切ります。電話をかけ、タスク 3 を与え、電話を切ります。方法 2: 電話をかけて、タスク 1、タスク 2、タスク 3 を説明し、電話を切ります。明らかに、2 番目の方法がより効率的なソリューションです。これはロック粗大化のプロセスです。
第四に、同期と揮発性の違い
synchronized と volatile はどちらも Java キーワードであり、どちらもスレッド セーフを解決します
そのため、面接時に一緒に質問されることがよくあります。
2つは実際には関係ありません。
同期:
1. 原子性を確保するために、ロックとロック解除によって一連のコードを結合します。
2. ロックとロック解除により、メモリの可視性が保証されます。
3. 命令の並べ替えには一定の制約があります。
揮発性:
1.原子性は保証できません。
2. メモリの可視性を確保します。
3. 命令の並べ替えは禁止されています。
同期はほとんどの場合、スレッドの安全性を保証できますが。ただし、同期はどのような状況でも使用できません。同期とは、一定の対価を支払うことです。同期は、ロックとロック解除によって保証されます。したがって、他のスレッドがロックを取得できない場合、そのスレッドはブロックされます。スレッドは CPU を放棄し、放棄した後に再度呼び出される時間は不明です。同期を使用する場合、高いパフォーマンスはある程度あきらめられます。volatile を使用してもスレッドのブロックは発生しませんが、パフォーマンスにも一定の影響がありますが、同期ほど大きくはありません。
効率化のためにマルチスレッドが使用されます。同期を使用することは、特定の効率をあきらめることを意味します。両者はバランスが取れている必要があります。