Java のさまざまなロックとは何ですか?

PHPz
リリース: 2023-04-27 17:43:07
転載
1579 人が閲覧しました

Java の 15 種類のロックの紹介

多くの同時実行に関する記事を読むと、フェア ロック、オプティミスティック ロックなど、さまざまなロックについて言及します。この記事では、さまざまなロックの分類を紹介します。紹介内容は以下の通りです。

公平なロック/不公平なロック

リエントラントロック/非リエントラントロック

排他ロック/共有ロック

ミューテックス ロック/読み取り/書き込みロック

楽観的ロック/悲観的ロック

セグメントロック

バイアスロック/軽量ロック/重量ロック

スピンロック

上記は錠の名詞です。これらの分類はすべてが錠の状態を指すものではありません。錠の特性を指すものや、錠のデザインを指すものもあります。以下にまとめた内容は、それぞれの錠についての一定の説明です。名詞。

公平なロック/不公平なロック

フェアロック

公平なロックとは、複数のスレッドがロックを申請した順序でロックを取得することを意味します。

不正なロック

不公平なロックとは、複数のスレッドがロックを取得する順序が、ロックを適用する順序と一致しないことを意味し、最初に適用したスレッドよりも後から適用したスレッドが先にロックを取得する可能性があります。優先順位の逆転や飢餓が発生する可能性があります。

Java ReentrantLock の場合、コンストラクターを通じてロックが公正なロックであるかどうかを指定します。デフォルトは不公平なロックです。不公平なロックの利点は、スループットが公平なロックよりも高いことです。同期の場合、これは不公平なロックでもあります。 ReentrantLock のような AQS を介したスレッド スケジューリングを実装していないため、公平なロックに変える方法はありません。

リエントラントロック/非リエントラントロック

リエントラントロック

広義のリエントラントロックとは、繰り返し再帰的に呼び出すことができるロックのことを指し、外層で使用した後も内層でデッドロックなく使用できる(同じオブジェクトまたはクラスであれば)。 ). このようなロックはリエントラントロックと呼ばれます。 ReentrantLock と synchronized は両方とも再入可能なロックです

synchronized void setA() が例外をスローします{

Thread.sleep(1000);

setB();

}

synchronized void setB() が例外をスローします{

Thread.sleep(1000);

}

上記のコードはリエントラント ロックの機能であり、リエントラント ロックでない場合、現在のスレッドで setB が実行されず、デッドロックが発生する可能性があります。

非リエントラントロック

非リエントラント ロックは、リエントラント ロックとは対照的に、再帰的に呼び出すことができず、再帰的に呼び出しを行うとデッドロックが発生します。スピン ロックを使用して非リエントラント ロックをシミュレートするという古典的な説明を見ました。コードは次のとおりです。

インポート java.util.concurrent.atomic.AtomicReference;

パブリック クラス UnreentrantLock {

private AtomicReference owner = new AtomicReference();

public void lock() {

スレッドの現在の = Thread.currentThread();

//この文は非常に古典的な「スピン」構文であり、AtomicInteger にも

があります の (;;) {

if (!owner.compareAndSet(null, current)) {

戻る;###### }

}

}

public void lock() {

スレッドの現在の = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

コードも比較的単純です。スレッドを格納するためにアトミック参照を使用します。同じスレッドが lock() メソッドを 2 回呼び出します。ロックを解放するために lock() を実行しないと、2 回目にスピンが呼び出されたときにデッドロックが発生します。このロックは再入可能ではありませんが、実際には同じスレッドが毎回ロックを解放してロックを取得する必要はなく、このようなスケジュールの切り替えは非常にリソースを消費します。

それを再入可能なロックに変更します:

インポート java.util.concurrent.atomic.AtomicReference;

パブリック クラス UnreentrantLock {

private AtomicReference owner = new AtomicReference();

プライベート int 状態 = 0;

public void lock() {

スレッドの現在の = Thread.currentThread();

if (current == owner.get()) {

州 ;###### 戻る;###### }

//この文は非常に古典的な「スピン」構文であり、AtomicInteger にも

があります の (;;) {

if (!owner.compareAndSet(null, current)) {

戻る;###### }

}

}

public void lock() {

スレッドの現在の = Thread.currentThread();

if (current == owner.get()) {

if (状態 != 0) {

州 - ;###### } それ以外 {###### owner.compareAndSet(current, null);

}

}

}

}

各操作を実行する前に、現在のロック保持者が現在のオブジェクトであるかどうかを判断し、毎回ロックを解放するのではなく状態カウントを使用します。

ReentrantLock でのリエントラント ロックの実装

ここでは、不公平なロックのロック取得方法を見ていきます:

最終ブール値 nonfairTryAcquire(int 取得) {

最終スレッド current = Thread.currentThread();

int c = getState();

if (c == 0) {

if (compareAndSetState(0, 取得)) {

setExclusiveOwnerThread(current);

true を返す;

}

}

//ここにあります

else if (current == getExclusiveOwnerThread()) {

int nextc = c を取得;

if (nextc

新しいエラーをスロー(「最大ロック数を超えました」);

setState(nextc);

true を返す;

}

false を返す;

}

プライベートの volatile int 状態は、再エントリの数をカウントし、頻繁な保持および解放操作を回避するために AQS で維持されます。これにより、効率が向上するだけでなく、デッドロックも回避されます。

排他ロック/共有ロック

排他的ロックと共有ロック: C.U.T パッケージの ReeReentrantLock と ReentrantReadWriteLock を読むと、一方が排他的ロックで、もう一方が共有ロックであることがわかります。

排他的ロック: このロックは一度に 1 つのスレッドのみが保持できます。

共有ロック: このロックは複数のスレッドで共有できます。典型的な例は、ReentrantReadWriteLock の読み取りロックです。読み取りロックは共有できますが、書き込みロックは一度に排他的にしかできません。

さらに、読み取りロックを共有すると、同時読み取りが非常に効率的になりますが、読み取りと書き込み、書き込みと読み取りは相互に排他的です。

排他的ロックと共有ロックも AQS を通じて実装され、排他的ロックまたは共有ロックを実現するためにさまざまな方法が実装されます。 Synchronized の場合は、もちろん排他ロックです。

ミューテックス ロック/読み取り/書き込みロック

ミューテックスロック

共有リソースにアクセスする前にロックし、アクセスが完了したらロックを解除します。ロック後、再度ロックしようとする他のスレッドは、現在のプロセスがロックを解除するまでブロックされます。

ロック解除時に複数のスレッドがブロックされている場合、ロック上のすべてのスレッドが準備完了状態にプログラムされ、準備完了になった最初のスレッドがロック操作を実行し、他のスレッドは再び待機します。このようにして、1 つのスレッドのみが、mutex

によって保護されたリソースにアクセスできます。 読み取り/書き込みロック

読み取り/書き込みロックは相互排他ロックと共有ロックの両方であり、読み取りモードは共有、書き込みモードは相互排他 (排他ロック) です。

読み取り/書き込みロックには、読み取りロック状態、書き込みロック状態、非ロック状態の 3 つの状態があります。 Java での読み取り/書き込みロックの具体的な実装は ReadWriteLock

です。 書き込みモードで読み取り/書き込みロックを保持できるスレッドは一度に 1 つだけですが、読み取りモードでは複数のスレッドが同時に読み取り/書き込みロックを保持できます。書き込み状態のロックを占有できるスレッドは 1 つだけですが、読み取り状態のロックは複数のスレッドが同時に占有することができるため、高い同時実行性を実現できます。書き込み状態ロック下にある場合、ロックを取得しようとするスレッドは、書き込み状態ロックが解放されるまでブロックされます。読み取り状態ロック下にある場合、他のスレッドは読み取り状態ロックを取得できますが、すべてのスレッドの読み取りステータス ロックが解放されるまで、書き込みステータス ロックを取得できません。読み取り/書き込みロックがスレッドの要求を感知したときに、書き込み操作を試行しようとするスレッドが書き込みステータス ロックを取得できないようにするためです。書き込みステータスのロックを取得するには、読み取りステータスのロックを取得しようとする後続のスレッドをすべてブロックします。したがって、読み取り/書き込みロックは、リソース上で書き込み操作よりも読み取り操作がはるかに多い状況に非常に適しています。

楽観的ロック/悲観的ロック

悲観的なロック

常に最悪のシナリオを想定してください。データを取得しに行くたびに、他の人がそのデータを変更するのではないかと考え、データを取得するたびにそのデータをロックします。このようにして、他の人がデータを取得しようとすると、彼らはデータを取得することになります。ロックを取得するまでブロックします (共有リソースは一度に 1 つのスレッドによってのみ使用され、他のスレッドはブロックされ、使用後にリソースは他のスレッドに転送されます)。このようなロック メカニズムの多くは、行ロック、テーブル ロック、読み取りロック、書き込みロックなど、従来のリレーショナル データベースで使用されており、操作前にすべてロックされます。 Java の synchronized ロックや ReentrantLock などの排他的ロックは、悲観的ロックの考え方を実装したものです。

楽観的ロック

常に最善の状況を想定し、データを取得しに行くたびに他人が変更しないだろうと考えてロックされないようにしますが、更新する際にはこの期間内に他人が更新したかどうかを判断します。バージョン番号メカニズムと CAS アルゴリズム実装を使用できます。オプティミスティック ロックは、スループットを向上させることができるマルチ読み取りアプリケーション タイプに適しています。データベースによって提供される write_condition メカニズムは、実際にはオプティミスティック ロックです。 Java では、java.util.concurrent.atomic パッケージの下のアトミック変数クラスは、楽観的ロックの実装メソッドである CAS を使用して実装されます。

セグメントロック

セグメント化されたロックは、実際にはロックの設計であり、特定のロックではありません。ConcurrentHashMap の場合、その同時実行性の実装は、セグメント化されたロックの形式で効率的な同時操作を実現することです。

同時実行コンテナ クラスのロック メカニズムは、粒度が小さいセグメント化されたロックに基づいており、セグメント化されたロックは、複数の同時実行プログラムのパフォーマンスを向上させる重要な手段の 1 つでもあります。

並行プログラムでは、シリアル操作によりスケーラビリティが低下し、コンテキストの切り替えによりパフォーマンスも低下します。これら 2 つの問題は、ロックで競合が発生した場合に発生します。制限されたリソースを保護するために排他ロックを使用する場合、それは基本的にシリアル方式であり、一度に 1 つのスレッドのみがそれにアクセスできます。したがって、スケーラビリティに対する最大の脅威は排他ロックです。

ロックの競合の度合いを減らすには、一般に 3 つの方法があります: 1. ロック保持時間を減らす 2. ロック要求の頻度を減らす 3. 調整メカニズムを備えた排他的ロックを使用する これらのメカニズムにより、より高い同時実行性が可能になります。

場合によっては、ロック分解手法をさらに拡張して、一連の独立したオブジェクトのロックを分解し、セグメント化されたロックにすることができます。

実際、簡単に言えば:

コンテナ内には複数のロックがあり、各ロックはコンテナ内のデータの一部をロックするために使用されます。そうすると、複数のスレッドがコンテナ内の異なるデータ セグメントのデータにアクセスするときに、スレッド間でロックの競合がなくなり、効果的に改善できます。同時アクセスの効率。これは、ConcurrentHashMap で使用されるロック セグメンテーション テクノロジです。まず、データがストレージ用のセグメントに分割され、次にデータの各セグメントにロックが割り当てられます。スレッドが 1 つのセグメントにアクセスするためにロックを占有する場合のデータに加えて、他のセグメントのデータにもアクセスでき、他のスレッドによってアクセスされます。

例: 16 個のロックを含む配列が ConcurrentHashMap で使用されます。各ロックはすべてのハッシュ バケットの 1/16 を保護し、N 番目のハッシュ バケットは (N mod 16) 番目のロックによって保護されます。合理的なハッシュ アルゴリズムを使用してキーを均等に配布すると仮定すると、ロック リクエストをおよそ 1/16 に減らすことができます。このテクノロジーにより、ConcurrentHashMap は最大 16 個の同時書き込みスレッドをサポートできます。

バイアスロック/軽量ロック/重量ロック

ロックステータス:

ロックフリー状態

バイアスロックステータス

軽量ロックのステータス

ヘビーウェイト ロック ステータス

ロックのステータスは、オブジェクト モニターのオブジェクト ヘッダー内のフィールドによって示されます。 4州は競争が進むにつれて徐々にエスカレートしていくが、これは不可逆的なプロセス、つまり格下げはできない。これら 4 つの状態は Java 言語のロックではなく、ロックの取得と解放 (同期使用時) の効率を向上させるために Jvm によって行われた最適化です。

バイアスロック

バイアスされたロックとは、同期コードの一部が常に 1 つのスレッドによってアクセスされ、そのスレッドが自動的にロックを取得することを意味します。ロックを取得するコストを削減します。

軽量

軽量ロックとは、ロックがバイアスされたロックであり、別のスレッドによってアクセスされると、バイアスされたロックが軽量ロックにアップグレードされることを意味します。他のスレッドはスピンを通じてロックを取得しようとするため、ブロックされず、パフォーマンスが向上します。

ヘビーウェイト ロック

重量ロックとは、ロックが軽量ロックの場合、別のスレッドが回転しているにもかかわらず、回転が永遠に続くわけではなく、一定回数回転してロックを取得できない場合、ブロッキングに入るという意味です。重量級のロックに拡張します。重いロックは他のアプリケーション スレッドをブロックし、パフォーマンスを低下させます。

スピンロック

CAS アルゴリズムはオプティミスティック ロックの実装方法であることは知られていますが、CAS アルゴリズムにはスピン ロックも含まれますので、ここではスピン ロックとは何かについて説明します。

CAS アルゴリズムの簡単なレビュー

CASとは英語のCompare and Swap(比較と交換)で、有名なロックフリーアルゴリズムです。ロックフリープログラミングとは、ロックを使用せずに複数のスレッド間で変数を同期すること、つまりスレッドをブロックせずに変数を同期することを意味するため、ノンブロッキング同期とも呼ばれます。 CAS アルゴリズムには 3 つのオペランドが含まれます

読み書きする必要があるメモリ値 V

比較する値 A

書き込まれる新しい値 B

変数を更新する場合、変数の期待値 A がメモリ アドレス V の実際の値と同じである場合にのみ、メモリ アドレス V に対応する値が B に変更され、そうでない場合は何も実行されません。一般に、これはスピン操作、つまり一定の再試行です。

スピンロックとは何ですか?

スピンロック(spinlock): スレッドがロックを取得する際、そのロックが別のスレッドによって取得されている場合、スレッドはループ内で待機し、ロックが取得されるまでロックが正常に取得できるかどうかを判断し続けます。ループを終了します。

共有リソースを保護するために提案されたロック機構です。実際、スピン ロックはミューテックス ロックに似ており、どちらも特定のリソースの相互排他的使用を解決するように設計されています。ミューテックス ロックであってもスピン ロックであっても、一度に存在できる保持者は最大 1 つ、言い換えれば、いつでも最大 1 つの実行ユニットがロックを取得できます。ただし、この 2 つのスケジュール メカニズムは若干異なります。ミューテックス ロックの場合、リソースがすでに占有されている場合、リソースの申請者はスリープ状態にしか入ることができません。ただし、スピン ロックは呼び出し元をスリープさせません。スピン ロックが別の実行ユニットによって保持されている場合、呼び出し元はそこをループし続けて、スピン ロックの保持者がロックを解放したかどうかを確認します。それがその名前が付けられた理由です。

Javaでスピンロックを実装するにはどうすればよいですか?

以下は簡単な例です:

パブリック クラス SpinLock {

private AtomicReference cas = new AtomicReference();

public void lock() {

スレッドの現在の = Thread.currentThread();

//CAS

を使用する while (!cas.compareAndSet(null, current)) {

// 何もしない###### }

}

public void lock() {

スレッドの現在の = Thread.currentThread();

cas.compareAndSet(current, null);

}

}

lock() メソッドは CAS を使用します。最初のスレッド A がロックを取得すると、正常に取得でき、while ループに入りません。この時点でスレッド A がロックを解放しない場合、別のスレッド B が再度ロックを取得します。この時点ではCASが満たされていないため、Aスレッドがunlockメソッドを呼び出してロックを解除するまで、whileループに入りCASが満たされているかどうかを判定し続けます。

スピン ロックに関する問題

1. スレッドがロックを長時間保持すると、ロックの取得を待機している他のスレッドがループに入り、CPU を消費します。不適切に使用すると、CPU 使用率が非常に高くなる可能性があります。 2. 上記の Java で実装されたスピン ロックは公平ではありません。つまり、待機時間の最も長いスレッドが最初にロックを取得することを満足させることができません。不公平なロックは「スレッド枯渇」問題を引き起こします。

スピンロックの利点

1. スピン ロックによってスレッドの状態が切り替わることはなく、常にユーザー状態になります。つまり、スレッドは常にアクティブになります。スピン ロックによってスレッドがブロッキング状態になることはなく、不必要なコンテキストの切り替えが減少します。 2. ノンスピン ロックが取得できない場合、ロックはブロッキング状態になり、カーネル状態に移行します ロックを取得した場合、カーネル状態から復帰する必要があり、スレッドが必要ですコンテキストの切り替え。 (スレッドがブロックされると、スレッドはカーネル (Linux) スケジューリング状態に入ります。これにより、システムはユーザー モードとカーネル モードの間で切り替わり、ロックのパフォーマンスに重大な影響を及ぼします)

リエントラントスピンロックと非リエントラントスピンロック

この記事の冒頭のコードを注意深く分析すると、このコードは再入性をサポートしていないことがわかります。つまり、スレッドが初めてロックを取得したときに、ロックが解放される前に再度ロックを再取得します。 2回目で成功。 CAS が満たされていないため、2 回目の取得は while ループに入って待機しますが、リエントラント ロックであれば 2 回目で正常に取得されるはずです。

また、たとえ2回目に取得できたとしても、1回目のロック解除時には2回目に取得したロックも解除されてしまうという理不尽さがあります。

リエントラント ロックを実装するには、ロックを取得したスレッドの数を記録するカウンターを導入する必要があります。

パブリック クラス ReentrantSpinLock {

private AtomicReference cas = new AtomicReference();

プライベート int カウント;

public void lock() {

スレッドの現在の = Thread.currentThread();

if (current == cas.get()) { // 現在のスレッドがロックを取得している場合、スレッド数を 1 つ増やして、

を返します。 カウント ;###### 戻る;###### }

//ロックが取得できない場合は、CAS

をスピンスルーします。 while (!cas.compareAndSet(null, current)) {

// 何もしない###### }

}

public void lock() {

スレッド cur = Thread.currentThread();

if (cur == cas.get()) {

if (count > 0) {//0 より大きい場合は、現在のスレッドが複数回ロックを取得していることを意味し、ロックの解放はカウントを 1 つ減らすことでシミュレートされます

カウント - ;###### } else {// count==0 の場合、ロックを解放できるため、ロックの取得回数とロックの解放回数が一致することが保証されます。

cas.compareAndSet(cur, null);

}

}

}

}

スピン ロックとミューテックス ロック

スピン ロックとミューテックス ロックはどちらも、リソース共有を保護するメカニズムです。

スピン ロックであってもミューテックス ロックであっても、存在できるホルダーは常に 1 つだけです。

ミューテックス ロックを取得するスレッドがすでに占有されている場合、スレッドはスリープ状態に入りますが、スピン ロックを取得するスレッドはスリープせず、ロックが解放されるまでループで待機し続けます。

以上がJava のさまざまなロックとは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:yisu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!