Javaの「ロック」(2018 Meituan Review技術記事コレクションのメモを読む)

ここに画像の説明を挿入

1.楽観的ロックと悲観的ロック

悲观锁データを使用するときは、データを変更するために別のスレッドである必要があることを信じてください。したがって、データを取得するときに最初にロックされ、データが別のスレッドによって変更されることを確認します。(同期とロック)データを使用するときにデータを変更する他のスレッドがあると
乐观锁考えて、ロックを追加しましたが、他のスレッドがない前に判断する新しいデータの場合は新しいスレッドこのデータこのデータが更新されていない場合、現在のスレッドは変更されたデータを正常に書き込みます。データが他のスレッドによって更新されている場合は、実装ごとに異なる操作(エラー報告や自動再試行など)が実行されます。
ここに画像の説明を挿入

  • ペシミスティックロックは、書き込み操作が多いシナリオに適しています。最初にロックすると、書き込み操作中にデータが正しいことを確認できます。
  • 楽観的ロックは、多くの読み取り操作があるシナリオに適しており、ロックなしの機能により、読み取り操作のパフォーマンスを大幅に向上させることができます。
// ------------------------- 悲观锁的调用方式 -------------------------
// synchronized
public synchronized void testMethod() {
		// 操作同步资源
}
// ReentrantLock
private ReentrantLock lock = new ReentrantLock(); // 需要保证多个线程使用的是同一个锁
public void modifyPublicResources() {
			lock.lock();
			// 操作同步资源
			lock.unlock();
}
// ------------------------- 乐观锁的调用方式 -------------------------
private AtomicInteger atomicInteger = new AtomicInteger(); // 需要保证多个线程使用的是同一个AtomicInteger
atomicInteger.incrementAndGet(); //执行自增1

CAS与同期

イデオロギーの観点から:

  • Synchronizedは悲観的なロックであり、プログラムの並行性が深刻であると悲観的に信じているため、それを防ぐ必要があります。
  • CASは楽観的ロックであり、プログラムの同時実行性はそれほど深刻ではないことを楽観視しているため、スレッドが更新を試み続けるようにします。

2.スピンロックVSアダプティブスピンロック

スピンロック

Javaスレッドをブロックまたはウェイクアップするには、オペレーティングシステムがCPU状態を切り替えて完了する必要があります。この状態遷移にはプロセッサ時間が必要です(コンテキスト切り替えには時間がかかります)。同期コードブロックの内容が単純すぎると、状態遷移にかかる時間がユーザーコードの実行時間より長くなる場合があります。
物理マシンに複数のプロセッサがあり、2つ以上のスレッドを同時に並行して実行できる場合は、ロックを要求する後者のスレッドがCPUの実行時間を放棄しないようにして、ロックを保持しているスレッドがロックを保持しているかどうかを確認できます。すぐにロックを解除します。

現在のスレッドを「しばらく待つ」には、現在のスレッドをスピンする必要があります。スピンの完了後に同期リソースをロックしたスレッドがロックを解除した場合、現在のスレッドはブロックせずに同期リソースを直接取得できます。 。、それにより、スレッドの切り替えのオーバーヘッドを回避します。これがスピンロックです。

ここに画像の説明を挿入
缺点:ブロッキングを置き換えることはできません。スピン待機はスレッド切り替えのオーバーヘッドを回避しますが、プロセッサ時間を消費します。
したがって、スピン待機時間には一定の制限が必要です。スピンが制限された回数を超え(デフォルトは10回、-XX:PreBlockSpinを使用して変更できます)、ロックが正常に取得されない場合、スレッドは次のようになります。一時停止。

スピンロックの実現原理-CAS、AtomicIntegerの自動インクリメント操作に対して安全でないことを呼び出すソースコードのdo-whileループはスピン操作です。値の変更が失敗した場合、スピンはループを介して実行されます。変更が成功するまで。

アダプティブスピンロック

アダプティブとは、スピンの時間(回数)が固定されなくなったことを意味します、同じロックでの前のスピン時間とロック所有者の状態によって決定されます。同じロックオブジェクトで、スピン待機がロックを正常に取得し、ロックを保持しているスレッドが実行されている場合、仮想マシンはこのスピンも再び成功する可能性が高いと見なし、スピンを許可します待機は続きます比較的長い時間。
特定のロックでスピンが正常に取得されない場合は、将来ロックを取得しようとするときにスピンプロセスを省略し、プロセッサリソースの浪費を避けるためにスレッドを直接ブロックすることができます。(TicketLock、CLHlockおよびMCSlock)。

3.ロックなしVSたわみロックVS軽量ロックVS重量ロック

メモリ内のJavaオブジェクトの構造(HotSpot仮想マシン)
ロックの除去:JITコンパイル中に、コンテキストがスキャンされて、競合できないロックが削除されます。(StringBufferのStringBuffer( "..."))
ロックの粗大化ロックの範囲を拡大し、ロックとロック解除の繰り返しを回避します(whileループ内)
MarkWordストレージ構造に
 Javaオブジェクトヘッダーストレージ構造
格納されているデータJavaオブジェクトヘッダーのMarkWordロックに従いますフラグ位置の変更
プログラムの実行に伴い、MarkWordが変更されます

バイアスロック

ほとんどの場合、ロックはマルチスレッドの競合がないだけでなく、常に同じスレッドによって複数回取得されます。スレッドがロックをより安価に取得できるようにするために、バイアスロックが導入されています。
コアアイデア:スレッドがロックを取得すると、ロックはバイアスモードになります。このとき、Mark Wordの構造はバイアスロック構造になります。スレッドが再度ロックを要求すると、同期操作を行う必要はありません。つまり、ロックを取得するスレッドです。MarkWordのロックマークビットがバイアスロックであり、現在のスレッドIDがMark WordのスレッドIDと等しいことを確認するだけで、関連する多くの操作を節約できます。アプリケーションをロックします。

グローバルセキュリティポイント(この時点では
バイトコード実行さていません

軽量ロック

バイアスされたロックは、1つのスレッドが同期ブロック入ると実行され、2番目のスレッドがロック競合に参加すると軽量ロックアップグレードされます。(スレッドは同期ブロックを交互に実行します)

ヘビーウェイトロック

複数のスレッドが同時に同じロックにアクセスすると、ヘビーウェイトロックに拡張されます。

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

ここに画像の説明を挿入
ロックステータス機能
4つのロック状態の特徴
ここに画像の説明を挿入
バイアスロックは、マークワードを比較することでロックの問題を解決し、CAS操作の実行を回避します。
軽量ロックは、CAS操作とスピンを使用してロックの問題を解決し、パフォーマンスに影響を与えるスレッドのブロックとウェイクアップを回避します。
ヘビーウェイトロックは、ロックを所有するスレッドを除くすべてのスレッドをブロックします。

4.フェアロックVSアンフェアロック

フェアロックと
は、複数のスレッドがロックを適用する順序でロックを取得することを意味します。スレッドはキューとキューに直接入り、キューの最初のスレッドがロックを取得できます。

  • 利点:ロックを待機しているスレッドが飢えて死ぬことはありません。
  • 短所:全体的なスループット効率は、不公平なロックよりも低くなります。待機キューの最初のスレッドを除くすべてのスレッドがブロックされ、ブロックされたスレッドをウェイクアップするCPUのオーバーヘッドが不公平なロックよりも大きくなります。
    フェアロック
    シングルウィンドウサービス処理、番号を取得した後、座席で待機し、番号に電話をかけ(目を覚ます)、ウィンドウに移動してサービスを処理します

不公平なロック
複数のスレッドがロックされている場合、それらは直接ロックを取得しようとし、取得されなくなるまで待機キューの終わりまで待機します。ただし、この時点でロックが利用可能である場合、このスレッドはブロックせずに直接ロックを取得できるため、ロックを適用するスレッドが最初にロックを取得するときに不公平なロックが発生する可能性があります。

  • 利点:スレッドはブロックせずに直接ロックを取得する機会があり、CPUがすべてのスレッドをウェイクアップする必要がないため、スレッドを呼び出すオーバーヘッドを削減でき、全体的なスループット効率が高くなります。
  • 短所:待機キュー内のスレッドは、餓死するか、ロックを取得する前に長時間待機する可能性があります。
    不当なロックの実装

ReentrantLockのソースコード:フェアロックとアンフェアロックの実現。

5.リエントラントロックVS非リエントラントロック

リエントラントロックは、再帰ロックとも呼ばれ、同じスレッドが外部メソッドでロックを取得すると、スレッドの内部メソッドが自動的にロックを取得することを意味します(ロックオブジェクトが同じオブジェクトまたはクラスである必要がある場合)。以前に取得したものの、リリースされていないためです。
ReentrantLockとJavaで同期されたものはどちらも再入可能ロックです。再入可能ロックの利点の1つは、デッドロックをある程度回避できることです。

public class Widget {
	public synchronized void doSomething() {
		System.out.println("方法1执行...");
		doOthers();
	}
	public synchronized void doOthers() {
		System.out.println("方法2执行...");
	}
}

クラス内の2つのメソッドは、同期された組み込みロックによって変更され、doOthers()メソッドがdoSomething()メソッドで呼び出されます。組み込みのロックは再入可能であるため、同じスレッドがdoOthers()を呼び出すときに現在のオブジェクトのロックを直接取得し、操作のためにdoOthers()を入力できます。

非再入可能ロックの場合、現在のスレッドは、doOthers()を呼び出す前に、doSomething()中に取得された現在のオブジェクトのロックを解放する必要があります。実際、オブジェクトロックは現在のスレッドによって保持されているため、解放できません。そのため、現時点ではデッドロックが発生します。
リエントラントロックの理解(村人が水を汲むために複数のバケツを持ってきました)

ただし、それが再入可能でないロックである場合、管理者はロックを同じ人のバケットにバインドすることのみを許可します最初のバケットがロックにバインドされた後、ロックは解放されません。その結果、2番目のバケットはロックにバインドできず、埋めることもできません。現在のスレッドはデッドロックされており、待機キュー全体のすべてのスレッドをウェイクアップすることはできません。非再入可能ロック

ReentrantLock非再入可能ロックNonReentrantLock

ソースコードの比較同期されたリソースが繰り返し呼び出されたときに、再入可能でないロックがデッドロックする理由。
ReentrantLockとNonReentrantLockはどちらも親クラスAQSを継承し、同期ステータスステータスは親クラスAQSで維持され、再エントリの数をカウントします。ステータスの初期値は0です。

スレッドがロックを取得しようとすると、リエントラントロックは最初にステータス値の取得と更新を試みます。status== 0の場合、他のスレッドが同期コードを実行していないことを意味し、ステータスは1に設定され、現在のスレッドが実行を開始します。 。status!= 0の場合、現在のスレッドがロックを取得したスレッドであるかどうかを判断し、そうである場合はstatus + 1を実行すると、現在のスレッドは再びロックを取得できます。

非再入可能ロックは、現在のステータス値を直接取得して更新しようとします。status!= 0の場合、ロックの取得に失敗し、現在のスレッドがブロックされます。
ソースコード
ロックが解放されると、現在のスレッドがロックを保持しているスレッドである場合、再入可能ロックも最初に現在のステータスの値を取得します。status-1 == 0の場合、現在のスレッドで繰り返されるすべてのロック取得操作が実行され、スレッドが実際にロックを解放することを意味します。
非再入可能ロックは、現在のスレッドがロックを保持しているスレッドであると判断した後、ステータスを直接0に設定し、ロックを解放します。

6.排他ロックVS共有ロック

排他ロックは排他ロックとも呼ばれます。つまり、ロックは一度に1つのスレッドでのみ保持できます。スレッドTがデータAに排他ロックを追加すると、他のスレッドはAにどのタイプのロックも追加できなくなります。排他ロック取得するスレッドは、データの読み取りと変更の両方を行うことができます
JDKで同期された実装クラスとJUCでロックされた実装クラスは相互排他ロックです。

共有ロックとは、複数のスレッドがロックを保持できることを意味します。スレッドTがデータAに共有ロックを追加する場合、他のスレッドは共有ロックをAにのみ追加でき、排他ロックを追加することはできません。共有ロックを取得するスレッドは、データの読み取りのみが可能で、データの変更はできません
ReentrantReadWriteLock
ReentrantReadWriteLockでは、読み取りロックReadLockと書き込みロックWriteLockの本体はSyncですが、読み取りロックと書き込みロックのロック方法は異なります。読み取りロックは共有ロックであり、書き込みロックは排他ロックです。
読み取りロックの共有ロックにより、同時読み取りが非常に効率的になり、読み取りロックと書き込みロックが分離されているため、読み取り、書き込み、読み取り、書き込みのプロセスが相互に排他的になります。したがって、ReentrantReadWriteLockの同時実行性は、一般的なミューテックスロックと比較して大幅に改善されています。

排他ロックでは、状態の値は通常0または1(再入可能ロックの場合、状態値は再入可能の数)であり、共有ロックでは、状態は保持されているロックの数です。
ここに画像の説明を挿入
ただし、ReentrantReadWriteLockには読み取りと書き込みの2つのロックがあるため、整数変数の状態での読み取りロックと書き込みロック(または状態)の数を記述する必要があります。したがって、状態変数は2つの部分にビット単位でカット」され、上位16ビットは読み取りロックステータス(読み取りロックの数)を表し、下位16ビットは書き込みロックステータス(書き込みロックの数)を表します。

  • 読み取りロックを保持しているスレッドの場合、スレッドは書き込みロックを取得できません
    書き込みロックを取得するときに、現在の読み取りロックが占有されていることが判明した場合、読み取りロックが使用されているかどうかに関係なく、取得はすぐに失敗します現在のスレッドによって保持されています)

  • スレッドが書き込みロックを保持している場合、スレッドは引き続き読み取りロックを取得できます
    読み取りロックの取得時に書き込みロックが占有されていることが判明した場合、書き込みロックが現在のスレッドによって占有されていない場合にのみ取得は失敗します)

スレッドが読み取りロックを取得すると、同時に読み取りロックを保持している他のスレッドが存在する可能性があるため、読み取りロックを取得したスレッドを書き込みロックに「アップグレード」することはできません。
また、書き込みロックを取得したスレッドの場合も同様です。、読み取りと書き込みを独占する必要があるため、読み取りロックを取得し続けることができます。書き込みロックと読み取りロックを同時に取得すると、書き込みロックを
解放して読み取りロックを保持し続けることもできます。 、書き込みロックが読み取りロックに「劣化」するようにします。

要約すると
、スレッドが書き込みロックと読み取りロックを同時に保持したい場合は最初に書き込みロックを取得してから読み取りロックを取得する必要があります。
書き込みロックは読み取りロックに「ダウングレード」できます。
読み取りロックはできません。ロックを書き込むために「アップグレード」されます。

ReentrantLockは、読み取り操作または書き込み操作に関係なく、排他ロックです。

おすすめ

転載: blog.csdn.net/eluanshi12/article/details/84771250