Java 並行プログラミング原則 3 (CountDownLatch、Semaphore、CopyOnWriteArrayList、ConcurrentHashMap)

一、カウントダウンラッチ、セマフォ:

1.1 カウントダウンラッチとは何ですか? 使用は何ですか?最下層はどのように実現されるのでしょうか?

CountDownLatch の本質は実際にはカウンターです。

マルチスレッドで業務を並行して処理する場合、他のスレッドの処理が完了するのを待ってからマージなどの後続操作を行う必要があります。ユーザーに応答する場合は、CountDownLatch を使用してカウントすることができます。他のスレッドが出現した後、メインスレッドは起きた。

CountDownLatch 自体は AQS に基づいて実装されています。

CountDownLatch を新規作成する場合は、特定の値を直接指定します。この値は state 属性にコピーされます。

子スレッドがタスクの処理を完了すると、countDown メソッドが実行され、内部状態が state - 1 に直接与えられます。

状態が 0 に減少すると、await によって一時停止されたスレッドが目覚めます。

CountDownLatch は再利用できず、使用後は冷却されます。

1.2 セマフォとは何ですか? 使用は何ですか?最下層はどのように実現されるのでしょうか?

セマフォは電流制限機能に使用できるツールクラスです。

たとえば、Hystrix にはセマフォ分離が含まれており、必要な同時スレッドの数が制限されているため、セマフォを使用して実装できます。

たとえば、現在のサービスが同時に動作するために最大 10 個のスレッドを必要とする場合、セマフォを 10 に設定します。タスクの送信でセマフォを取得する必要はなく、作業に進み、終了したらセマフォを返すだけです。

セマフォも AQS に基づいて実装されます。

セマフォ構築時はセマフォリソースの数を指定、取得時は取得するセマフォの数を指定 CAS ではアトミック性が保証されており、リターンも同様です。

1.3 メインスレッドが終了するとプログラムは停止しますか?

メインスレッドが終了しても、実行中のユーザースレッドがまだある場合、メインスレッドは終了しません。

メインスレッドが終了したら、残りはデーモンスレッドです、終了!

二、CopyOnWriteArrayList:

2.1 CopyOnWriteArrayList はスレッドの安全性をどのように確保しますか? 何か欠点はありますか?

CopyOnWriteArrayList がデータを書き込むとき、ReentrantLock に基づいてアトミック性が保証されます。

次に、データを書き込む場合、コピーがコピーされて書き込まれ、書き込みが成功すると、CopyOnWriteArrayList の配列に書き込まれます。

データを読み取る際には、データの不整合がないことを確認してください。

データ量が比較的大きい場合、データを書き込むたびにコピーする必要があり、多くのスペースを占有します。データ量が比較的大きい場合、CopyOnWriteArrayList の使用はお勧めできません。

書き込み操作はアトミック性を確保するために必要であり、読み取り操作は同時であることが保証されており、データ量は大きくありません~

3、コンカレントハッシュマップ(JDK1.8)

3.1 HashMap がスレッドセーフではないのはなぜですか?

質問 1: JDK1.7 (拡張中) にループがあります。

問題 2: データが上書きされ、データが失われる可能性があります。

質問 3: 次に、これも従来の ++ であるカウンタは、要素の数と HashMap が書き込まれた回数を記録するときに不正確に記録します。

質問 4: データの移行と容量の拡張によっても、データの損失が発生する可能性があります。

3.2 ConcurrentHashMap はスレッドの安全性をどのように確保しますか?

1: テールプラグ、その後 CAS で拡張してスレッドの安全性を確保

2: 配列への書き込み時は CAS に基づいて安全性が保証され、リンクリストへの挿入や赤黒ツリーへの挿入時は synchronized に基づいて安全性が保証されます。

3: ここで、ConcurrentHashMap は LongAdder によって実装されたテクノロジーであり、最下層は依然として CAS です。(アトミックロング)

4: ConcurrentHashMap が拡張する場合、1 つのポイントは CAS に基づいて、データ移行で同時実行の問題が発生しないようにすることです。2 番目に、ConcurrentHashMap は同時拡張操作も提供します。たとえば、配列の長さは 64 から 128 に拡張されます。2 つのスレッドが同時に拡張される場合、

スレッド A は 64 ~ 48 インデックスのデータ移行タスクを受け取り、スレッド B は 47 ~ 32 インデックスのデータ移行タスクを受け取ります。重要なのは、タスクを受信するときに、スレッドの安全性を確保するために CAS に基づいているということです。

3.3 ConcurrentHashMap が構築された後、配列は作成されますか? そうでない場合、配列の初期化におけるスレッドの安全性をどのように保証できますか?

ConcurrentHashMap は遅延読み込みメカニズムであり、ほとんどのフレームワーク コンポーネントは遅延読み込みです~

初期化スレッドの安全性を確保するために CAS に基づいています。これには、スレッド初期化データのアトミック性を制御するために CAS が sizeCtl 変数を変更するだけでなく、DCL も使用されます。外側の層は配列が初期化されていないと判断し、途中でCASを元にsizeCtlを変更し、配列の未初期化判定を行います。

画像.png

3.4 負荷係数はなぜ 0.75 で、長さが 8 に達するとリンク リストが赤黒のツリーに変わるのはなぜですか?

また、ConcurrentHashMap の負荷係数は変更できません。

負荷率 0.75 は 2 つの方法で説明できます。

なぜ 0.5 ではないのか、なぜ 1 ではないのか?

0.5: 負荷率が 0.5 の場合、データの半分を追加すると拡張が開始されます。

  • 利点: ハッシュの衝突が少なく、クエリ効率が高い。
  • 短所: 拡張が頻繁に行われ、スペース使用率が低い。

1: 負荷係数が 1 の場合、データは配列の長さに追加されて拡張が開始されます。

  • 利点: 容量拡張が頻繁に行われないため、スペースを有効に利用できます。
  • 短所: ハッシュの競合が特に頻繁に発生し、リンク リストでデータがハングするため、クエリの効率に影響します。また、リンク リストですら赤黒ツリーを生成するには長すぎるため、書き込みの効率に影響します。 。

両方の側面を考慮すると、0.75 は中間の選択と言えます。

ポアソン分布について話しましょう。負荷係数が 0.75 の場合、ポアソン分布によれば、リンク リストの長さが 8 に達する確率は非常に低くなります。ソース コード内のロゴは 0.00000006 で、赤黒の木は非常に低いです。

ConcurrentHashMap では赤黒ツリーが導入されていますが、赤黒ツリーは書き込みのメンテナンス コストが高いため、可能であれば使用しても構いません。HashMap のソース コードのコメントにも、赤黒ツリーは可能な限り避ける必要があると記載されています。

6 のリンク リストへの縮退については、ツリーが 7 つの値でいっぱいであるためであり、リンク リストと赤黒ツリー間の頻繁な変換を防ぐために 7 は縮退されません。ここで、6 は縮退して中間値を残します。頻繁な変換は避けてください。

プット操作が多すぎるシーンは拡張期にプットの詰まりを引き起こすのでしょうか?

通常は詰まりの原因にはなりません。

put 操作中に現在のインデックス位置にデータがないことが判明した場合、データは通常どおり古い配列に削除されるためです。

put 操作中に、現在の位置データが新しいアレイに移行されていることが判明し、現時点では正常に挿入できない場合は、容量の拡張を支援し、拡張操作をすぐに終了し、インデックスを再選択します。位置クエリ

3.5 ConcurrentHashMap はいつ拡張されますか?また、拡張プロセスはどのようなものですか?

  • ConcurrentHashMap の要素数が負荷率計算のしきい値に達した場合、容量を直接拡張します
  • 大量のデータをクエリするために putAll メソッドが呼び出される場合、直接展開操作が発生する場合もあります。挿入されたデータが次の展開のしきい値よりも大きい場合、大量のデータは直接展開されてから挿入されます。
  • 配列の長さが 64 未満で、リンクされたリストの長さが 8 以上の場合、拡張がトリガーされます。画像.png

展開プロセス: (sizeCtl は int 型の変数で、初期化と展開の制御に使用されます)

  • 各拡張スレッドは、oldTable の長さに基づいて拡張識別スタンプを計算する必要があります (2 つの拡張スレッドの配列長の不一致を避けるため)。次に、拡張識別スタンプの 16 ビットが 1 であることを確認して、左シフトします。 16 ビットの場合は負の数になります)
  • 最初に拡張されたスレッドは sizeCtl + 2 になります。これは、容量を拡張できるスレッドが現在 1 つあることを意味します。
  • 最初の拡張スレッドを除いて、他のスレッドは sizeCtl + 1 を増加します。これは、別のスレッドが拡張を支援するようになったことを意味します。
  • 最初のスレッドは新しい配列を初期化します。
  • 各スレッドはデータ移行タスクを受け取り、oldTable のデータを newTable に移行します。デフォルトでは、各スレッドは毎回長さ 16 の移行データ タスクを受け取ります。
  • データ移行が完了し、各スレッドが再びタスクを要求しようとすると、受け取るタスクがないことが判明したため、拡張を中止し、sizeCtl - 1 を設定します。
  • 容量拡張を終了する最後のスレッドは、-1 を検出した後、残り 1 つがあり、容量拡張を終了する最後のスレッドは、最初から最後まで再度チェックして、移行されていない残りのデータがあるかどうかを確認します (これはという状況は基本的には起こりません)をチェックした後、-1することでsizeCtlが差し引かれて展開が完了します。

3.6 ConcurrentHashMap のカウンターはどのように実装されていますか?

これは LongAdder の仕組みに基づいて実装されていますが、LongAdder の参照を直接利用するのではなく、LongAdder の原理に従って類似度 80% 以上のコードを記述し、それを直接利用しています。

LongAdder は CAS を使用して追加を行ってアトミック性を確保し、次にセグメント ロックに基づいて同時実行性を確保します。

3.7 ConcurrentHashMap の読み取り操作はブロックされますか?

どこにチェックを入れてもブロックされません。

クエリ配列? : 最初のブロックは、要素が配列内にあるかどうかを確認し、それを直接返します。

リンクされたリストをクエリしますか? : 2 番目のブロックは、特別な状況がない場合、リンクされたリストの次と次をクエリするだけです。

拡張するときは?: 3 番目のブロックで、現在のインデックス位置が -1 の場合、現在の位置にあるすべてのデータが新しい配列に移行されたことを意味し、展開されたかどうかに関係なく、新しい配列に直接移動してクエリを実行できます。完了したかどうか。

赤黒木について質問しますか? : スレッドが赤黒ツリーに書き込んでいる場合、読み取りスレッドはこの時点でも赤黒ツリーにクエリを実行できますか? 赤黒の木はバランスをとるために回転する場合があり、回転によってポインタが変化し、問題が発生する可能性があるためです。したがって、赤黒ツリーを変換する際には、赤黒ツリーだけでなく二重リンクリストも保持することになり、その際、読み取りスレッドがブロックされないように二重リンクリストを問い合わせることになります。赤黒ツリーに書き込み中、書き込み待ち、読み取り中のスレッドがあるかどうかの判断方法は、TreeBinのlockStateで判断し、1であれば書き込み中のスレッドがあり、2であれば書き込み中であることを示します。 4n の場合は、書き込みを待機している書き込みスレッドがあることを意味します。これは、読み取り操作を実行している複数のスレッドがあることを意味します。

おすすめ

転載: blog.csdn.net/lx9876lx/article/details/129116483