マルチスレッド(高度)

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

悲観的なロック:
常に最悪のケースを想定してください。データを取得するたびに、他の人がデータを変更すると思います。そのため、データを取得するたびにロックされ、データを取得したい他の人がデータを取得するまでブロックされます。ロック。
ロックの競合の可能性が高いと予想する
楽観的ロック:
データは一般に同時実行の競合を生成しないと想定されているため、データが送信および更新されると、データに同時実行の競合があるかどうかが正式に検出されます。実行してください。
ロックの競合の可能性は低いと予想されます。

クラスメートAとクラスメートBが先生に質問したい
クラスメートAは、「先生は忙しいので、質問します。先生は自由に答えられないかもしれません」と考えているため、クラスメートAはにメッセージを送信します。先生第一:「先生、
忙しいですか?午後2時に質問してもいいですか?」(ロック操作に相当)肯定的な答えが出て初めて、本当に質問になります。 。
否定的な答えが出たら、しばらく待ってから、次回は先生に時間を確認してください。これは悲観的なロックです。
クラスメートBは、「先生は比較的自由です。質問します。先生はしたがって、クラスメートBが直接教師のところに来ます。(
ロックなし、リソースへの直接アクセス)教師が本当に忙しい場合は、当面の問題は解決します。教師が本当に忙しい場合は、クラスメートB
は教師の邪魔をせず、次回も戻ってきます(ただし、ロックされていませんが、データアクセスの競合を識別できます)。これは楽観的なロックです。
これら2つのアイデアは、優れているとか劣っているとは言えませんが、現在のシーンが適切かどうかを確認します。
現在の教師が本当に忙しい場合は、悲観的なロックを使用する戦略がより適切です。楽観的なロックを使用すると、「何度も無駄」になり、
追加のリソースを消費します。
現在の教師が本当にアイドル状態の場合、楽観的ロックを使用する戦略がより適切であり、悲観的ロックを使用すると効率が低下します。
一般に
悲観的なロック、より多くの作業、より多くのコスト、より効率の悪い
楽観的なロック、より少ない作業、より低いコスト、より効率的な

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

通常のミューテックスの場合、ロックとロック解除の2つの操作しかありません。

2つのスレッドが同じオブジェクトをロックしている限り、相互排除が発生します
読み取り/書き込みロックの場合、3つの操作に分けられます。
読み取りロックの追加:コードが読み取り操作のみを実行する場合は、読み取りロック
を追加します。書き込みロックを追加します。コードが変更されている場合は、書き込みロックを追加します。ロックを
解除します。


読み取りロックと読み取りロックの場合、読み取りロックと書き込みロックの間には相互排除関係はなく、書き込みロックと書き込みロックの間には相互排除のみが必要です。

ヘビーウェイトロックとライトウェイトロック

ヘビーウェイトロックはより多くのオーバーヘッドでより多くのことを実行します
ライトウェイトロックはより少ないオーバーヘッドでより少ないことを実行します
通常、ペシミスティックロックはヘビーウェイトロックであり、オプティミスティックロックは軽量ロックであると理解できます
。カーネル(オペレーティングシステムのmutexインターフェイスの呼び出しなど)では、一般にヘビーウェイトロックと見なされます(オペレーティングシステムのロックは、スレッドをブロックして待機するなど、カーネル内で多くのことを実行します。 ..)
ロックが純粋なユーザーモードで実装されている場合、現時点では一般的に軽量ロックと見なされます(ユーザーモードのコードはより制御可能で効率的です)

待機ロックとスピンロックの一時停止

一時停止された待機ロックは、多くの場合、カーネルのいくつかのメカニズムを介して実装されます。これは、多くの場合、より重く[重量ロックの一般的な実装]
スピンロックは、多くの場合、ユーザーモードコードを介して実装され、多くの場合、より軽量です[軽量ロック]の一般的な実装]
スピンロックとサスペンド待機ロックの理解
女神を追いかけることを想像してみてください。男の子が女神に告白したとき、女神はこう言い
ました。 ...長い間、突然、女神は私に「やってみるべきか」というメッセージを送ってくれました(
この長い時間の間に、女神はいくつかの男性の票を変えたかもしれないことに注意してください)。
スピンロック:死んだ肌と忍耐力。まだ毎日女神と話しているおはようございますそしておやすみなさい女神が前の女神と別れたら
すぐにその地位に就く機会をつかむことができます

フェアロックvsアンフェアロック

これら2つは混乱しやすいかもしれませんが、
フェアロックを覚えておいてください。複数のスレッドがロックを待機している場合、先に来る人が最初にロックを取得します(先着順の原則に従います)
不公平なロック:複数のスレッドが同じロックを待機します、先着順の原則に従わず、スレッドがロックを取得するのを待つ各人の確率は等しい;(オペレーティングシステムの場合、自身のスレッド間のスケジューリングはランダム(等しい機会)、ミューテックスオペレーティングシステムによって提供されるロックは、不公平なロックです

再入可能ロックと非再入可能ロック

ロックのスレッドは2回続けてロックでき、デッドロックします。これは非再入可能ロックです。デッドロックしない場合は、再入可能ロックです。
「自分を閉じ込める」を理解する
スレッドはロックを解放せず、//最初のロックを再ロックしようとします。
ロックは成功し
ますlock();
// 2番目のロック、ロックは占有され、待機をブロックします
。lock();
例:
a鍵のかかったトイレに人が飛び込んできて、中から出られない、外から出られない
一般的に使用される同期ロックについて話す
1:楽観的ロックと悲観的ロックの両方(競争の激しさに応じて、適応型)
2:読み取り/書き込みロックではなく、通常のミューテックスロック
3:重量級ロックと軽量ロックの両方(競争の激しさによると、適応型)
4:軽量ロックはスピンに基づいて部分的に実装され、重量ロックは保留中のロックに基づいて実装されます
5:不公平なロック
6:リエントラントロック

CAS

CASとは:
CAS:フルネームコンペアアンドスワップ、文字通り:「コンペアアンドスワップ」、CASには次の操作が含ま
れます。メモリ内の元のデータV、古い期待値A、および必要な新しい値Bを想定します。変更されます。

  1. AとVが等しいかどうかを比較します。(比較)
  2. 等しいと比較する場合は、BをVに書き込みます。(両替)
  3. 操作が成功したかどうかを返します。
    複数のスレッドが同時にリソースに対してCAS操作を実行する場合、1つのスレッドのみが正常に動作できますが、他のスレッドはブロックされず、他のスレッド
    は操作が失敗したという信号のみを受信します。
    CASは楽観的なロックと見なすことができます(またはCASは楽観的なロックの実装であると理解できます)
    CASの最大の重要性は、このスレッドセーフコードを記述して、
    両方のCASに新しいアイデアと方向性を提供することです。 2はできますか?スレッドセーフの問題を解決するのにどのように役立ちますか?

CASをベースに「アトミッククラス」を実現

(Java標準ライブラリは、CASに基づいて変更でき、スレッドセーフなロック用のint、long、int配列をカプセル化するために一般的に使用されるアトミッククラスのセットを提供します)

import java.util.concurrent.atomic.AtomicInteger;

public class Demo27 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        AtomicInteger num = new AtomicInteger(0);

        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 50000; i++) {
    
    
                // 这个方法就相当于 num++
                num.getAndIncrement();
            }
        });
        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 50000; i++) {
    
    
                // 这个方法就相当于 num++
                num.getAndIncrement();
            }
        });

//        // ++num
//        num.incrementAndGet();
//        // --num
//        num.decrementAndGet();
//        // num--
//        num.getAndDecrement();
//        // += 10
//        num.getAndAdd(10);

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        // 通过 get 方法得到 原子类 内部的数值.
        System.out.println(num.get());
    }
}

ここに画像の説明を挿入このコードにはスレッドセーフの問題はありません。CASによって実装された++操作に基づいて、同期よりもスレッドセーフで効率的であることが保証されます。
同期にはロックの競合が発生し、2つのスレッドがそれぞれを待機する必要があります。その他。CASは、++がスレッドセーフである理由をアピールするの
を待つスレッドブロッキングを含みません。

ここでは、CAS
==キーの理解について説明します:==このコード全体は++を実装し、CASはこれら3つのアクションの比較、+ 1、および交換も実行します。失敗した場合、交換は行われません。
擬似コード:
ここに画像の説明を挿入

ここに画像の説明を挿入

スピンロックはCASに基づいて実装できます

偽のコード:
ここに画像の説明を挿入

CASにおけるABA問題

ABAの質問:
2つのスレッドt1とt2があるとします。初期値がAの共有変数numがあります。
次に、スレッドt1がCASを使用してnumの値をZに変更する場合は、
最初にnumの値を読み取り、それをoldNum変数に記録します。CASを使用して
numの現在の値がAであるかどうかを判断し、Aの場合はZに変更します。
ただし、t1によって実行される2つの操作の間で、t2スレッドはnumの値をから変更する場合があります。 AからB、そして再びBから
Aのスレッドt1のCASに変更された場合、numは変更されないままであると予想されますが、numの値はt2によって変更されています。これはAに変更されただけです
。 t1はnumの値をZに更新しますか?
この時点で、t1スレッドは、現在の変数が常にAであるか、変更プロセスを受けているかを区別できません。

ABA問題によるバグ
ほとんどの場合、t2スレッドなどの繰り返しの水平ジャンプの変更は、t1がnumを変更するかどうかには影響しませんが、一部の特殊なケースを除外することはできません。

おかしな人が100の預金を持っていると仮定します。おかしいは、ATMから50ドルを引き出したいと考えています。ATMは、-50
操作を同時に実行するために2つのスレッドを作成します。一方
のスレッドは-50を正常に実行し、もう一方のスレッドは-50を失敗すると予想します。
CASを使用してこの控除プロセスを完了すると、問題が発生する可能性があります。
通常のプロセス

  1. デポジット100。スレッド1は現在のデポジット値100を取得し、それを50に更新することを期待します。スレッド2は現在のデポジット値100を取得し、
    それを50に更新することを期待します。
  2. スレッド1は控除を正常に実行し、デポジットは50に変更されます。スレッド2はブロックされ、待機しています。
  3. 実行するのはスレッド2の番であり、現在のデポジットは50であり、以前に読み取られた100とは異なり、実行は失敗します。
    異常なプロセス
  4. デポジット100。スレッド1は現在のデポジット値100を取得し、それを50に更新することを期待します。スレッド2は現在のデポジット値100を取得し、
    それを50に更新することを期待します。
  5. スレッド1は控除を正常に実行し、デポジットは50に変更されます。スレッド2はブロックされ、待機しています。
  6. スレッド2を実行する前に、おかしな友達がおかしな友達に50を転送するだけで、アカウントの残高が100になります!!
  7. 実行するスレッド2の番で、現在のデポジットが100であることがわかります。これは、前に読み取った100と同じであり、控除操作が再度実行されます。
    このとき、控除操作は2回実行されます。 !!!それはすべてABA問題の幽霊です。

解決
変更する値のバージョン番号を紹介します。CASはデータの現在の値を古い値と比較するときに、バージョン番号が期待どおりかどうかも比較します
。CAS操作は古い値を読み取りながらバージョン番号も読み取ります。
実際の変更現在
のバージョン番号が読み取りバージョン番号と同じである場合、データを変更し、バージョン番号+1を追加します。
現在のバージョン番号が読み取りバージョン番号よりも大きい場合、操作は失敗します(データが変更されました) 。
上記の転送例と比較してください。
おかしな人が100の預金を持っていると仮定します。おかしい人はATMから50元を引き出したいと考えています。ATMは2つのスレッドを作成して-50操作を同時に実行
ます。
スレッドは-50を正常に実行し、他のスレッド-50は失敗しまし
た。ABAの問題を解決するには、最初に1に設定されたバージョン番号を天びんに割り当てます。

  1. デポジット100。スレッド1はデポジット値100、バージョン番号1を取得し、50への更新を期待します。スレッド2はデポジット値100、
    バージョン番号1を取得し、50への更新を期待します。
  2. スレッド1は控除を正常に実行し、デポジットは50に変更され、バージョン番号は2に変更されます。スレッド2はブロックされ、待機しています。
  3. スレッド2を実行する前に、面白い友達は50を面白いものに転送し、アカウントの残高は100になり、バージョン番号は3になります。
  4. 実行するのはスレッド2の番であり、現在のデポジットは100であり、これは前に読み取った100と同じですが、現在のバージョン番号は3、
    前に読み取ったバージョン番号は1、バージョンは現在のバージョンよりも小さく、操作は失敗したと見なされます。

同期化された最適化メカニズム

ロックの肥大化/ロックのエスカレーション

これに適応するために同期された能力を反映しています
ここに画像の説明を挿入

ロックの粗大化

細分化、粗大化、ここでの厚さは「ロックの粒度」
とロックコードに含まれる範囲を指します
。太字のコードの範囲が大きいほど、ロックの粒度は粗くなります。
範囲が細かいほど、
ここに画像の説明を挿入
粒度。比較的薄い場合は、複数のスレッド間の同時実行性が高くなります。
厚い場合は、ロックとロック解除のオーバーヘッドが小さくなります。

コンパイラーは最適化を行い、どこかのロックの粒度が細かすぎる場合は粗くなると自動的に判断します
。この最適化が実行され、間隔が小さい場合(中央の間隔のコードが少ない場合)、この最適化がトリガーされる可能性があります

ロックの取り外し

一部のコードは明らかにロックする必要はありません。ロックを追加すると、コンパイラはロックが不要であると判断し、ロックを強制終了します。

ロック操作があまり明白でない場合があり、この誤った決定が誤って行われます。

StringBuffer、Vector ...は標準ライブラリでロックされており、アピールされたクラスは単一のスレッドで使用されます。つまり、単一のスレッドがロックされます。

おすすめ

転載: blog.csdn.net/chenbaifan/article/details/124016766