【2023年Javaインタビュー8部作】Javaマルチスレッド

目次

1. マルチスレッドについて教えてください

2. Java はどこで CAS を使用しますか

3. AQS (Abstract Queue Synchronizer) についての理解について話してください

4. スレッドセーフを確保する方法について話す

5. 知っているスレッド同期方法について話す

6. 同期の使い方と原理について話す

7.同期とロックの違いは何ですか

8. Java で一般的に使用されるロックと原則について話す

9. volatile の使い方と原理について話す

10. スレッドの 6 つの状態について話す

11. ThreadLocal についてのあなたの理解について話してください

12. 知っているスレッド通信方式について話す

13. スレッドの作成方法について話す

14. スレッドプールについてのあなたの理解について話してください

15. 知っているスレッドセーフなコレクションは何ですか?

16. HashMap はスレッドセーフですか? そうでない場合、それを解決する方法は?

17. JUCについて教えてください

18. ConcurrentHashMap について教えてください


1. マルチスレッドについて教えてください

得点ポイント

スレッドとプロセスの関係、なぜマルチスレッドを使うのか

スレッド:スレッドはオペレーティング システムのスケジューリングの最小単位であり、プロセスが複数のタスクを同時に処理できるようにします。

関係:プロセス内に複数のスレッドを作成できます。これらのスレッドは、独自のカウンター、スタック、ローカル変数を持ち、プロセス内のリソースを共有できます。リソースが共有されているため、プロセッサはこれらのスレッドをすばやく切り替えることができ、これらのスレッドが同時に実行されているような印象をユーザーに与えます。

マルチスレッドの利点:スレッドがブロック状態または待機状態になると、他のスレッドが CPU の実行権を取得できるため、CPU の使用率が向上します。

マルチスレッドのデメリット:デッドロックが発生する可能性がある、頻繁なコンテキストの切り替えによりリソースが浪費される可能性がある、並行プログラミングでリソースの制約によりマルチスレッドのシリアル実行が実行される場合、速度がシングルスレッドよりも遅くなる可能性がある.

デッドロック:実行中のリソースの競合により、複数のスレッドが互いに待機するデッドロックを指します。外力がなければ、前進することはできません。

詳細:

したがって、プロセス内に複数のスレッドを作成できます。これらのスレッドは、独自のカウンター、スタック、ローカル変数を持ち、プロセス内のリソースを共有できます。リソースが共有されているため、プロセッサはこれらのスレッドをすばやく切り替えることができ、これらのスレッドが同時に実行されているような印象をユーザーに与えます。

一般に、オペレーティング システムは同時に複数のタスクを実行でき、各タスクはプロセスです。プロセスは複数のタスクを同時に実行でき、各タスクはスレッドです。プログラムの実行後、少なくとも 1 つのプロセスが存在し、プロセスには複数のスレッドを含めることができますが、少なくとも 1 つのスレッドを含める必要があります。

マルチスレッドを使用すると、開発者に大きなメリットがもたらされます。マルチスレッドを使用する主な理由は次のとおりです。

  • 1. CPU コアの増加現代のコンピューター プロセッサのパフォーマンスを向上させる方法は、主な周波数の追求からより多くのコアの追求へと変化しているため、プロセッサのコア数が増加し、プロセッサの能力を最大限に活用できます。プログラムのパフォーマンスを向上させます。プログラムは、計算ロジックを複数のプロセッサコアに分散できるマルチスレッドテクノロジを使用して、プログラムの処理時間を大幅に短縮し、プロセッサコアを追加することでより効率的になります。
  • 2. 応答時間の短縮 複雑なビジネスでは複雑なコードを書かなければならないことがよくあります. マルチスレッド技術を使用すると、データの一貫性が弱い操作を他のスレッドにディスパッチして、画像のアップロード、電子メールの送信などの処理 (またはメッセージ キュー) を行うことができます。 、注文の生成など このようにして、ユーザー要求に応答するスレッドはできるだけ早く処理を完了できるため、応答時間が大幅に短縮され、ユーザー エクスペリエンスが向上します。
  • 3. より優れたプログラミング モデル Java は、マルチスレッド プログラミングに優れた一貫性のあるプログラミング モデルを提供するため、開発者は問題解決に集中することができます. 開発者は、頭を悩ませることなく、この問題に適したビジネス モデルを構築するだけで済みます. マルチスレッドの実装方法を検討してください. . 開発者がビジネス モデルを確立すると、Java が提供するマルチスレッド プログラミング モデルに少し変更を加えて簡単にマッピングできます。

2. Java はどこで CAS を使用しますか

得点ポイント

アトミック クラス、AQS、並行コンテナー

標準的な答え

CAS、コンペア アンド スワップは、マルチスレッド並列処理の場合にロックの使用によって引き起こされるパフォーマンスの低下を解決するメカニズムです。

CAS 演算は、メモリ位置 (V)、期待される古い値 (A)、および新しい値 (B) の 3 つのオペランドで構成されます。メモリ位置の値が予想される元の値と一致する場合、プロセッサはその位置の値を新しい値で自動的に更新します。それ以外の場合、プロセッサは何もしません。

CAS は事実上、「場所 V には値 A が含まれているはずだと思います。もしそうなら、この場所に B を置きます。そうでない場合は、その場所を変更しないでください。この場所が現在何であるかを教えてください。

 Java が提供する API で CAS が使用される場所は数多くありますが、典型的な使用シナリオには、アトミック クラス、AQS、コンカレント コンテナーなどがあります

原子クラス 

アトミック クラスの場合、内部に多数のアトミック操作メソッドが用意されています。整数値のアトミック置換、指定値の増加、1 の加算など、これらのメソッドの最下層は、オペレーティング システムによって提供されるCAS アトミック命令によって実現されます単一の変数のスレッド セーフ問題を解決するために使用できます

AtomicInteger を例にとると、スレッドがオブジェクトの incrementAndGet() メソッドを呼び出すと、CAS を使用してその値を変更しようとします。この時点で他のスレッドが値を操作しない場合、値は正常に変更されます。それ以外の場合、CAS は変更が成功するまで操作が繰り返し実行されます。

AQS (アブストラクト キュー シンクロナイザー)

これは、マルチスレッド シンクロナイザーの基本的なフレームワークであり、多くのコンポーネントがこのフレームワークを使用して、状態変数の値を変更することにより、ロックおよびロック解除操作を実行します。複数のスレッドがロックをめぐって競合する場合は、CAS を使用してステータス コードを変更し、変更に成功した場合はロックを取得し、変更に失敗した場合は同期キューに入って待機します。

AQS の場合、同期キューの末尾にノードを追加すると、まず CAS の形で 1 回試行し、失敗するとスピン状態になり、CAS の形で試行を繰り返します。また、同期状態が共有的に解除されると、同期状態も CAS 的に変更されます。

同時コンテナー 

ConcurrentHashmap などの同期機能を備えた一部のコンテナーは、Synchronize+CAS+Node を使用して同期を実現します。配列の初期化、配列の拡張、リンク リスト ノードの操作のいずれであっても、最初に CAS が使用されます。 

並行コンテナーの場合、内部で CAS 操作を複数回使用する ConcurrentHashMap を例にとります。

配列を初期化すると、CAS モードで初期化状態が変更され、複数のスレッドが同時に初期化されるのを回避します。

put メソッドを実行してヘッド ノードを初期化すると、初期化されたヘッド ノードが CAS モードで指定されたスロットの最初の位置に設定され、複数のスレッドが同時にヘッド ノードを設定することを回避します。

配列を拡張するとき、各スレッドは CAS モードでタスク シーケンス番号を変更して、拡張タスクを競合し、他のスレッドとの競合を回避します。

get メソッドを実行すると、他のスレッドが同時にヘッド ノードを変更しないように、head で指定されたスロットのヘッド ノードを CAS の方法で取得します。

おまけの答え - アトミック操作命令

CAS の実装は、オペレーティング システムのアトミック命令のサポートと切り離せません. Java でアトミック命令をカプセル化する方法は、参照型のアトミック置換、int 整数のアトミック置換、および長整数。これらのメソッドには、var1、var2、var4、var5 の 4 つのパラメーターがあります。ここで、var1 は操作対象のオブジェクトを表し、var2 は置換されるメンバー変数を表し、var4 は期待値を表し、var5 は更新された値を表します。public final native boolean compareAndSwapObject( Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt( Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong( Object var1, long var2, long var4、long var6);

3. AQS ( Abstract Queue Synchronizer )についてのあなたの理解について話してください

得点ポイント

テンプレート メソッド、同期キュー、同期状態

標準的な答え

最下層 

AQS is the basic framework of a multi-threaded synchronizer. 多くのコンポーネントは、このフレームワークを使用して、状態変数の値を変更することにより、ロックおよびロック解除操作を実行します。複数のスレッドがロックをめぐって競合する場合は、CAS を使用してステータス コードを変更し、変更に成功した場合はロックを取得し、変更に失敗した場合は同期キューに入って待機します。 

1.概念: AQS は、ロックの基本的なフレームワークを構築するために使用される抽象キュー シンクロナイザーです.ロック実装クラスはすべて AQS に基づいて実装されます.

2.テンプレート メソッド: AQS はテンプレート メソッド パターンに基づいて設計されているため、ロックの実装は AQS を継承し、それによって指定されたメソッドを書き換える必要があります。

3.同期キューと同期状態: AQS は内部で FIFO キューを定義してスレッド同期を実現し、同期状態を定義してロック情報を記録します。

AQSのテンプレート方式は、同期状態を管理するロジックを抽出して標準的な処理を形成するもので、主に同期状態の排他取得、同期状態の排他解放、同期状態の共有取得、同期状態の共有解放などがあります。

テンプレート メソッド: AQS はテンプレート メソッド パターンに基づいて設計されているため、ロックの実装は AQS を継承し、その指定されたメソッドを書き換える必要があります。

AQS は内部的に FIFO キューを定義してスレッド同期を実現し、同期状態を定義してロック情報を記録します

AQSのテンプレート方式は、同期状態を管理するロジックを抽出して標準的な処理を形成するもので、主に同期状態の排他取得、同期状態の排他解放、同期状態の共有取得、同期状態の共有解放などがあります。

同期ステータスの排他取得を例にとると、その大まかな流れは次のとおりです。

 1. 排他的に同期状態を取得しようとします。

 2. ステータスの取得に失敗した場合は、現在のスレッドを同期キューに追加します。

 3. スピン処理の同期状態。現在のスレッドがキューの先頭にある場合は、ウェイクアップしてキューから出します。それ以外の場合は、ブロッキング状態にします。その中には、親クラスでは判別できないステップがあるため、それらは空のメソッドに抽出され、サブクラスが実装するために残されます。例えば、フェアロックとアンフェアロックでは最初のステップでの試行操作が異なるため、サブクラスはこのメソッドを実装する際にシーンに応じて実装する必要があります。

同期キュー: AQS の同期キューは双方向リンク リストであり、AQS はリンク リストの先頭ノードと末尾ノードを保持します。テールノードの設定はマルチスレッド競合があるため、CASの手法を用いて修正しています。ヘッドノードの設定については、同期状態を取得したスレッドで処理する必要があるため、ヘッドノードの変更は CAS メソッドを使用する必要はありません。

同期状態: AQS の同期状態は int 型の整数であり、状態を表すと同時に量を表すことができます。通常、ステータスが 0 の場合はロックなしを意味し、ステータスが 0 より大きい場合はリエントラント ロックの数を意味します。さらに、読み取り/書き込みロックのシナリオでは、このステータス フラグは読み取りロックと書き込みロックの両方を記録する必要があります。したがって、ロックの実装者は状態表現を上位部分と下位部分に分割し、上位ビットには読み取りロックが格納され、下位ビットには書き込みロックが格納されます。並行環境で同期状態を変更する必要があるため、スレッドセーフである必要があることに答える追加のポイント。AQS 自体はロック実装ツールであるため、ロックを使用して別のロックを定義する場合は、単に synchronized を使用するため、スレッド セーフを確保するためにロックを使用することは適していません。実際、同期状態は volatile によって変更されます。これにより、状態変数のメモリ可視性が保証され、スレッド セーフの問題が解決されます。並行環境で同期状態を変更する必要があるため、スレッドセーフである必要があることに答える追加のポイント。AQS 自体はロック実装ツールであるため、ロックを使用して別のロックを定義する場合は、単に synchronized を使用するため、スレッド セーフを確保するためにロックを使用することは適していません。実際、同期状態は volatile によって変更されます。これにより、状態変数のメモリ可視性が保証され、スレッド セーフの問題が解決されます。

4. スレッドセーフを確保する方法について話す

得点ポイント

アトミック クラス、揮発性、ロック

標準的な答え

スレッド セーフの問題: マルチスレッドのコンテキストでは、スレッドが期待どおりに実行されず、共有変数の操作で例外が発生します。

リソースの占有率に応じて軽いものから重いものまで並べられ、スレッドの安全性はアトミック クラス、揮発性、およびロックによって保証されます 。

アトミック クラスと volatile は1 つの共有変数のスレッド セーフのみを保証できますが、ロックはクリティカル セクション内の複数の共有変数のスレッド セーフを保証できます。

原子クラス

アトミック クラスは、アトミック操作の特性 (アトムは化学における最小単位であり、分割できません) を持つクラスであり、アトミックとは操作が中断できないことを意味します。複数のスレッドが同時に実行されている場合でも、操作が開始されると、他のスレッドによって妨げられることはありません。.

パッケージの下にはjava.util.concurrent.atomic、"Atomic" で始まる一連のクラスがあり、総称してアトミック クラスと呼ばれます。たとえば、AtomicInteger の代わりにint最下層は CAS アトミック命令によって実装され、内部ストレージの値は volatile で変更されるため、複数のスレッド間の変更が表示されます

AtomicInteger を例にとると、スレッドがオブジェクトの incrementAndGet() メソッドを呼び出すと、CAS を使用してその値を変更しようとします。この時点で他のスレッドが値を操作しない場合、値は正常に変更されます。それ以外の場合、CAS は変更が成功するまで操作が繰り返し実行されます。

揮発性

volatile キーワードは、順序と可視性を確保するために使用される軽量の同期メカニズムです。順序性: volatile で宣言された変数の前のコードはその前に実行する必要があり、その後のコードはそれよりも遅く実行する必要があります。Visibility: Once a variable is modified, it is immediately refreshed to the shared memory. 他のスレッドがこの変数を読み取りたい場合、最終的には自分のワークスペースではなくメモリから読み取ります。

ロック

ロックする方法は 2 つあります。つまり、synchronized キーワードと Lock インターフェイス(JUC パッケージの下) です。

同期ロックは、インスタンス メソッド、静的メソッド、およびコード ブロックに適用できる相互排他ロックであり、同時に 1 つのスレッドのみがコードを実行することを保証し、スレッドの安全性を確保します。実行が完了するか、例外が発生すると、ロックは自動的に解放されます。Synchronized locks are based on object headers and Monitor objects. 1.6 以降では、軽量ロックやバイアス ロックなどの最適化が導入されています。  

lockインターフェイスは、lock メソッドと unlock メソッドを介してコードの一部をロックできます。Lock実装クラスはすべて AQS に基づいて実装されていますLock は、ロックを待機しているスレッドを割り込みに応答させることができますが、synchronized はできません。synchronized を使用すると、待機中のスレッドは永久に待機し、割り込みに応答できなくなります。

スレッド セーフ:共有データを持つ複数のスレッドによって並列に実行されるプログラムでは、スレッド セーフ コードは、同期メカニズムを通じて各スレッドが正常かつ正しく実行できることを保証し、データ汚染などのアクシデントは発生しません 

Java でスレッド セーフを確保する方法は多数ありますが、その中で一般的に使用される 3 つの方法があり、リソースの占有状況に応じて軽いものから重いものへと並べられています. スレッド セーフを確保する 3 つの方法は、アトミック クラス、揮発性、およびロックです。

JDK は 1.5 以降、java.util.concurrent.atomic パッケージを提供しています。このパッケージのアトミック操作クラスは、単純な使用法、高いパフォーマンス、およびスレッド セーフで変数を更新する方法を提供します。

アトミックパッケージには全部で 17 個のクラスが提供されており、アトミック更新メソッドは、その機能によって、アトミック更新基本型、アトミック更新参照型、アトミック更新属性、アトミック更新配列の 4 種類に分類できます。アトミック更新のタイプに関係なく、「比較と置換」の規則に従う必要があります。つまり、更新する値が期待値と等しいかどうかを比較し、等しい場合は更新し、そうでない場合は失敗します。Volatile は、マルチプロセッサ開発における共有変数の「可視性」を保証する軽量の同期化であり、それによって単一の変数を読み書きする際のスレッドの安全性を保証します。可視性の問題は、プロセッサ コアのキャッシュが原因で発生します。各コアには独自のキャッシュがあり、これらのキャッシュはメモリと同期されます。Volatile には次のメモリ セマンティクスがあります: volatile 変数を書き込むと、スレッド ローカル メモリ内の共有変数の値がすぐにメイン メモリに更新されます。volatile 変数を読み取ると、スレッド ローカル メモリが無効になり、スレッドの読み取りが強制されます。メインメモリから直接共有変数。アトミック クラスと volatile は 1 つの共有変数のスレッド セーフのみを保証できますが、ロックはクリティカル セクション内の複数の共有変数のスレッド セーフを保証できます. Java でロックするには、synchronized キーワードと Lock インターフェイスの 2 つの方法があります. Synchronized は比較的初期の API であり、設計の開始時にタイムアウト メカニズム、ノンブロッキング フォーム、および複数の条件変数を考慮していませんでした。これらの比較的複雑な機能をサポートするようにアップグレードする場合は、文法構造を変更する必要があり、これは古いコードとの互換性を助長しません。そのため、JDK 開発チームは 1.5 で Lock インターフェースを追加し、Lock を介して上記の機能をサポートしました。つまり、割り込みへの応答のサポート、タイムアウト メカニズムのサポート、ノンブロッキング方式でのロックの取得のサポート、および複数の条件変数 (ブロッキング キュー) )。

ボーナス回答

スレッド セーフを実現するには、上記の 3 つの方法に加えて、次の方法があります。

  • 1. ステートレス設計のスレッド セーフの問題は、複数のスレッドによる共有変数の同時変更によって引き起こされる. 同時環境で共有変数が設計されていない場合、当然、スレッド セーフの問題は発生しません。この種のコード実装は「ステートレス実装」と呼ぶことができ、いわゆる状態は共有変数を参照します。
  • 2. イミュータブルな設計 並行環境で共有変数を設計する必要がある場合は、共有変数が読み取り専用かどうかを優先する必要があります. 読み取り専用のシナリオであれば、共有変数を不変として設計できるので、自然に.スレッドの安全性の問題は表示されません。具体的には、変数の前に final 修飾子を追加して、変更されないようにし、変数が参照型の場合は、不変型として設計します (String クラスを参照)。
  • 3. 同時実行ツール java.util.concurrent パッケージは、スレッドの安全性を確保できるいくつかの便利な同時実行ツール クラスを提供します。 - セマフォ: 特定のリソースに同時にアクセスするスレッドの数を制御できるセマフォです。- CountDownLatch: 1 つ以上のスレッドが、他のスレッドが操作を完了するまで待機できるようにします。- CyclicBarrier: スレッドのグループがバリアに到達するとブロックされ、バリアは最後のスレッドがバリアに到達するまで開かれず、バリアによってブロックされたすべてのスレッドが引き続き実行されます。
  • 4. ローカル ストレージ 変数を格納するために ThreadLocal を使用することも検討できます. ThreadLocal は、スレッドごとに個別のデータを簡単に格納できます。つまり、同時にアクセスする必要があるリソースを複数のコピーにコピーできます。このようにして、共有変数へのマルチスレッド アクセスを回避することができ、それらは独自の排他的なリソースにアクセスします。これにより、複数のスレッド間のデータ共有が根本的に分離されます。 

5. 知っているスレッド同期方法について話す

得点ポイント

同期、ロック

標準的な答え

スレッドの同期:つまり、スレッドがメモリ上で動作している場合、他のスレッドはこのメモリ アドレスを操作できません.スレッドが操作を完了するまで、他のスレッドはメモリ アドレスを操作できますが、他のスレッドは待機状態にあります.

Java は主にLocking を介してスレッド同期を実装します。ロックには、 synchronized キーワードと Lock インターフェース(JUC パッケージの下)の 2 種類があります。

同期ロックは、インスタンス メソッド、静的メソッド、およびコード ブロックに適用できる相互排他ロックであり、同時に 1 つのスレッドのみがコードを実行することを保証し、スレッドの安全性を確保します。実行が完了するか、例外が発生すると、ロックは自動的に解放されます。Synchronized locks are based on object headers and Monitor objects. 1.6 以降では、軽量ロックやバイアス ロックなどの最適化が導入されています。  

lockインターフェースはlock および unlock メソッドを介してコードの一部をロックでき、Lock 実装クラスはすべて AQS に基づいて実装されます。Lock を使用すると、ロックを待機しているスレッドを割り込みに応答させることができますが、synchronized を使用すると、待機中のスレッドは永久に待機し、割り込みに応答できなくなります。

Synchronized は、3 つの異なる使用方法に対応する 3 つの異なる位置に追加できます。これら 3 つの方法の違いは、ロック オブジェクトが異なることです。

1. 共通メソッドに追加された場合、ロックは現在のインスタンス (this) です。

2. 静的メソッドに追加されたロックは、現在のクラスの Class オブジェクトです。

3. コード ブロックに追加します。キーワードの後の括弧内にオブジェクトをロック オブジェクトとして明示的に指定する必要があります。

異なるロック オブジェクトは異なるロック粒度を意味するため、通常は問題を解決できますが、何も考えずにメソッドの前に追加するべきではありません。代わりに、ロックするスコープに従ってロック オブジェクトを正確に選択する必要があります。これにより、ロックの粒度を正確に判断し、ロックによって引き起こされるパフォーマンス オーバーヘッドを削減できます。

Synchronized は比較的初期の API であり、設計の開始時にタイムアウト メカニズム、ノンブロッキング フォーム、および複数の条件変数を考慮していませんでした。アップグレードによってこれらの比較的複雑な機能をサポートするためにsynchronizedが必要な場合は、古いコードとの互換性を助長しない文法構造を変更する必要があります。そのため、JDK 開発チームは 1.5 で Lock インターフェイスを導入し、Lock を通じて上記の機能をサポートしました。

Lock がサポートする機能には、応答割り込みのサポート、タイムアウト メカニズムのサポート、ノンブロッキング方式でのロックの取得のサポート、および複数の条件変数 (ブロッキング キュー) のサポートが含まれます。

ボーナス回答

synchronized采用“CAS+Mark Word”实现,为了性能的考虑,并通过锁升级机制降低锁的开销。在并发环境中,synchronized会随着多线程竞争的加剧,按照如下步骤逐步升级:无锁、偏向锁、轻量级锁、重量级锁。

Lock则采用“CAS+volatile”实现,其实现的核心是AQS。AQS是线程同步器,是一个线程同步的基础框架,它基于模板方法模式。在具体的Lock实例中,锁的实现是通过继承AQS来实现的,并且可以根据锁的使用场景,派生出公平锁、不公平锁、读锁、写锁等具体的实现。 

6.说说synchronize的用法及原理

得分点

作用于三个位置、对象头、锁升级

标准回答 

synchronized锁: 

synchronized锁是互斥锁,可以作用于实例方法、静态方法、代码块,能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。synchronized锁基于对象头和Monitor对象,在1.6之后引入轻量级锁、偏向锁等优化。  

作用于三个位置:

 1. 作用在静态方法上,则锁是当前类的Class对象。

 2. 作用在普通方法上,则锁是当前的实例(this)。

 3. 作用在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。

对象头: synchronized的底层是采用Java对象头来存储锁信息的,并且还支持锁升级。

锁升级:当竞争小的时候,只需以较小的代价加锁,直到竞争加剧,才使用重量级锁,从而减小了加锁带来的开销。

Synchronized は、3 つの異なる使用方法に対応する 3 つの異なる位置で使用できます.これら 3 つの方法の違いは、ロック オブジェクトが異なることです。異なるロック オブジェクトは異なるロック粒度を意味するため、通常は問題を解決できますが、何も考えずにメソッドの前に追加するべきではありません。代わりに、ロックするスコープに従ってロック オブジェクトを正確に選択する必要があります。これにより、ロックの粒度を正確に判断し、ロックによって引き起こされるパフォーマンス オーバーヘッドを削減できます。

オブジェクト ヘッダー

同期化の最下層では、Java オブジェクト ヘッダーを使用してロック情報を格納しロックのアップグレードもサポートします。

Java オブジェクト ヘッダーは、Mark Word、Class Metadata Address、および Array lengthの 3 つの部分で構成されます。このうち、Mark Word はオブジェクトの hashCode とロック情報を格納するために使用され、Class Metadata Address はオブジェクト型のポインタを格納するために使用され、Array length は配列オブジェクトの長さを格納するために使用されます。オブジェクトが配列型でない場合、配列の長さ情報はありません。

同期されたロック情報には、ロック フラグとロック ステータスが含まれます。これらはすべて、オブジェクト ヘッダーのマーク ワード部分に格納されます。

ロックの取得と解放によるパフォーマンスの消費を抑えるために、 Java 6ではバイアス ロックと軽量ロックが導入されています

そのため、Java 6 では、ロックは 4 つの状態に分けられ、低い方から高い方へ、ロックなし状態、偏ったロック状態、軽量ロック状態、重量ロック状態となります。スレッド競合の激化に伴い、ロックの状態は、ロックのない状態から重いロック状態に徐々にアップグレードされます。Locks can be upgrade but not downgraded . アップグレードのみを行い、ダウングレードは行わないというこの戦略は、効率を向上させるためのものです。

Synchronized の初期の設計にはロック アップグレード メカニズムが含まれていなかったため、パフォーマンスが低く、当時はロック フリー ポイントとロック可能ポイントしかありませんでした。バイアス ロックと軽量ロックはパフォーマンスを向上させるために導入されているため、これら 2 つの状態の原則とその違いに注目する必要があります。

バイアスロック:

バイアス ロックは、その名前が示すように、ロックが特定のスレッドに偏っていることを意味します。

スレッドが同期ブロックにアクセスしてロックを取得すると、ロック バイアスされたスレッド ID がオブジェクト ヘッダーに格納され、ロック レコードがスタック フレームに格納されます。将来的に同期ブロックを終了すると、自分のスレッド ID が Mark Word に格納されているかどうかを簡単にテストするだけで済みます。軽量ロックとは、ロック時に、JVM が最初に現在のスレッド スタック フレームにロック レコードを格納するためのスペースを作成し、マーク ワードをロック レコードにコピーすることを意味します。これは、正式には Displaced Mark Word と呼ばれます。次に、スレッドは CAS モードでマーク ワードをロック レコードへのポインタに置き換えようとします. 成功すると、現在のスレッドがロックを取得します. 失敗すると、他のスレッドがロックをめぐって競合することを意味します. このとき、現在のスレッドは、スピンしてロックを取得しようとします。

おまけの答え - ロックのアップグレードのプロセス

次に、実際のシーンから始めて、ロックのアップグレード プロセスについて詳しく説明します。 

1. 最初は、同期ブロックにアクセスするスレッドはなく、同期ブロックはロックフリーの状態です。

 2. 次に、スレッド 1 は最初に同期ブロックにアクセスし、マーク ワードを CAS (比較と交換) の形式で変更し、バイアス ロックを追加しようとしますこの時点では競合が発生していないため、バイアス ロックが正常にロックされ、この時点でスレッド 1 の ID がマーク ワードに格納されます。

 3. 次に、スレッド 2 が同期ブロックへのアクセスを開始し、マーク ワードを CAS モードで変更し、バイアス ロックを追加しようとします。このときの競合により、偏ったロックのロックが失敗するため、スレッド 2 が偏ったロックを解除するプロセス (スレッド 1 の ID をクリアする) を開始するため、同期ブロックはスレッド 1 の偏った状態から回復します。公正な競争の状態。

 4. 次に、スレッド 1 とスレッド 2 が競合し、軽量ロックを追加しようとして、同時に CAS モードでマーク ワードを変更します。競合のため、スレッド 1 が成功すると仮定すると、1 つのスレッドのみが成功します。しかし、スレッド 2 は簡単にあきらめず、スレッド 1 がすぐに実行を終了し、すぐに実行権が自分に落ちると考えて、スレッド 2 を回転させてロックし続けます。

 5. 最後に、スレッド 1 の実行がすぐに終了すると、スレッド 2 は軽量ロックの追加に成功し、ロックはヘビーウェイト状態に昇格されません。また、スレッド 1 の実行時間が長い場合、スレッド 2 は一定回数のスピン後にスピンを放棄し、ロック拡張のプロセスを開始します。その際、ロックはスレッド 2 によって重量ロックに変更され、スレッド 2 はブロック状態になります。スレッド 1 がロックまたはロック解除を繰り返すと、CAS 操作が失敗し、この時点でロックが解放され、待機中のスレッドが起動されます。

つまり、ロック アップグレード メカニズムの下では、ロックは 1 つのステップで重量級のロックになるのではなく、競争状況に応じて徐々にアップグレードされます。競合が小さいときは、少ないコストでロックすればよく、競合が激化するまで重いロックは使用されないため、ロックによるオーバーヘッドが削減されます。

7.同期とロックの違いは何ですか

得点ポイント

使い方、主な機能、実装メカニズム

標準的な答え

ロックと同期には次の違いがあります。

  1) インターフェイスとキーワード。Lock はインターフェースであり、synchronized は Java のキーワードであり、synchronized は組み込み言語の実装です。

  2) デッドロックの問題。Synchronized は例外が発生したときにスレッドが保持しているロックを自動的に解放するため、デッドロックは発生しませんが、Lock は例外が発生したときに unLock() によって積極的にロックを解放しませんが、デッドロックを引き起こす可能性が高いため、Lock を使用する場合、finally ブロックでロックを解除する必要があります。

  3) ロックを待っているスレッドが割り込みに応答するようにします。Lock を使用すると、ロックを待機しているスレッドを割り込みに応答させることができますが、synchronized を使用すると、待機中のスレッドは永久に待機し、割り込みに応答できなくなります。

  4) ロックが正常に取得されたかどうかを確認します。Lock を介して、ロックが正常に取得されたかどうかを知ることができますが、同期はできません。

  5) 効率比較。ロックは、複数のスレッドの読み取り操作の効率を向上させることができます。パフォーマンスに関しては、リソースの競合が激しくない場合、2 つのパフォーマンスは類似しており、リソースの競合が非常に激しい(つまり、同時に競合する多数のスレッドがある)場合は、 Lock のパフォーマンスは、synchronized よりもはるかに優れていますしたがって、具体的な使用状況に応じて選択する必要があります。

synchronized と Lock はどちらも、スレッド同期の手段であるロックです. これらの違いは、主に次の 3 つの側面に反映されています。

1. 使い方の違いsynchronized キーワードは、静的メソッド、インスタンス メソッド、コード ブロックで使用でき、明示的にロックを取得および解放する必要がない暗黙的なロックであるため、非常に便利です。この同期方法では、モニター (同期モニター) に依存してスレッド通信を実現する必要があります。キーワードが静的メソッドに作用する場合、Monitor は現在のクラスの Class オブジェクトです。キーワードがインスタンス メソッドに作用する場合、Monitor は現在のインスタンス (this) です。キーワードがコード ブロックに作用する場合は、次の括弧は、オブジェクトを Monitor として明示的に指定します。Lock インターフェイスは明示的なロックです, つまり, 内部で定義されたメソッドを呼び出して明示的にロックおよびロック解除する必要があります. synchronized と比較すると, これは少し面倒ですが, より大きな柔軟性を提供します. この同期方法では、Lock オブジェクトによって作成され、Lock に依存するスレッド通信を実現するために Condition オブジェクトに依存する必要があります。各 Condition は待機キューを表し、Lock は複数の Condition オブジェクトを作成できます。比較的言えば、各モニターも待機キューを表しますが、同期は 1 つのモニターしか持つことができません。したがって、Lock インターフェースは、スレッド通信の実装においてより高い柔軟性を備えています。

2. 機能の相違点Synchronized は初期の API であり、Lock は JDK 1.5 で導入されました。設計の面では、Lock は Synchronized の欠点を補います. Synchronized では利用できないいくつかの新機能を追加します. これらの機能には次のものが含まれます: - 非ブロックでロックを取得する: このメソッドは、呼び出しの直後に戻り、ロックを取得できる場合は true を返し、それ以外の場合は false を返します。- タイムアウトでロックを取得できる: スレッドがタイムアウト時間後にロックを取得しておらず、スレッドが中断されていない場合は、false を返します。

3. 実装メカニズムの違い同期化の最下層は Java オブジェクト ヘッダーを使用してロック情報を格納します. オブジェクト ヘッダーには、Mark Word、Class Metadata Address、および Array length の 3 つの部分が含まれます。このうち、Mark Word はオブジェクトの hashCode とロック情報を格納するために使用され、Class Metadata Address はオブジェクト型のポインタを格納するために使用され、Array length は配列オブジェクトの長さを格納するために使用されます。AQS は、ロックを構築するための基本的なフレームワークであるキュー シンクロナイザーです。ロック実装クラスはすべて AQS に基づいて実装されます。AQS はテンプレート メソッド パターンに基づいて設計されているため、ロックの実装は AQS を継承し、指定されたメソッドを書き換える必要があります。AQS は、FIFO キューを内部的に定義してスレッド同期を実現し、同期状態を定義してロック情報を記録します。初期の同期パフォーマンスは貧弱で、ロックほどではありません。その後、synchronized はその実装にロック アップグレード メカニズムを導入しましたが、そのパフォーマンスは Lock に負けていません。したがって、同期とロックの違いは主にパフォーマンスにあるわけではありません。2 つのパフォーマンスはほぼ同じだからです。ロックの取得と解放によるパフォーマンスの消費を抑えるために、Java 6 ではバイアス ロックと軽量ロックが導入されています。そのため、Java 6 以降、ロックは 4 つの状態に分割され、レベルは低いものから高いものへと、ロックなし、偏ったロック、軽量のロック、重量のロックとなります。スレッド競合の激化に伴い、ロックの状態は、ロックのない状態から重いロック状態に徐々にアップグレードされます。ロックはアップグレードできますが、ダウングレードはできません。アップグレードのみを行い、ダウングレードは行わないというこの戦略は、効率を向上させるためのものです。Synchronized の初期の設計には、ロック アップグレード メカニズムは含まれていませんでした。バイアス ロックと軽量ロックはパフォーマンスを向上させるために導入されているため、これら 2 つの状態の原則とその違いに注目する必要があります。バイアス ロックは、その名前が示すように、ロックが特定のスレッドに偏っていることを意味します。スレッドが同期ブロックにアクセスしてロックを取得すると、ロック バイアスされたスレッド ID がオブジェクト ヘッダーに格納され、ロック レコードがスタック フレームに格納されます。将来的に同期ブロックを終了すると、自分のスレッド ID が Mark Word に格納されているかどうかを簡単にテストするだけで済みます。軽量ロックとは、ロック時に、JVM がまず現在のスレッド スタック フレームにロック レコードを格納するためのスペースを作成し、マーク ワードをロック レコードにコピーすることを意味します。正式には Displaced Mark Word と呼ばれます。次に、スレッドは CAS モードでマーク ワードをロック レコードへのポインタに置き換えようとします. 成功すると、現在のスレッドがロックを取得します. 失敗すると、他のスレッドがロックをめぐって競合することを意味します. このとき、現在のスレッドは、スピンしてロックを取得しようとします。

8. Java で一般的に使用されるロックと原則について話す

得点ポイント

オブジェクト ヘッダー、AQS

標準的な答え

Java で一般的に使用されるロック: synchronized キーワードと lock lock インターフェイス。

synchronizedキーワードの最下層では、 Java オブジェクトヘッダーを使用してロック情報を格納します。

ロック インターフェイスは、 AQS (Abstract Queue Synchronizer)に基づいて実装されています。AQS は、ロック同期を実現するための先入れ先出しキューを内部的に定義し、ロック情報を記録するための同期状態も定義します。

AQS は、状態変数の値を変更することにより、ロックおよびロック解除操作を実行します。複数のスレッドがロックをめぐって競合する場合は、CAS を使用してステータス コードを変更し、変更に成功した場合はロックを取得し、変更に失敗した場合は同期キューに入って待機します。 

Java でロックするには、synchronized キーワードと Lock インターフェースの 2 つの方法があり、Lock インターフェースの古典的な実装は ReentrantLock です。さらに、ReadWriteLock インターフェイスがあり、それぞれ読み取りと書き込み用の 2 つのロックを内部的に設計します。どちらも Lock タイプであり、その古典的な実装は ReentrantReadWriteLock です。このうち、synchronized の実装はオブジェクト ヘッダーに依存し、Lock インターフェイスの実装は AQS に依存します。

同期

同期化の最下層はJava オブジェクトヘッダーを使用してロック情報を格納します. オブジェクト ヘッダーには、Mark Word、Class Metadata Address、および Array length の 3 つの部分が含まれます。このうち、Mark Word はオブジェクトの hashCode とロック情報を格納するために使用され、Class Metadata Address はオブジェクト型のポインタを格納するために使用され、Array length は配列オブジェクトの長さを格納するために使用されます。

ロック

AQS は、ロックを構築するための基本的なフレームワークであるキュー シンクロナイザーです。ロック実装クラスはすべて AQS に基づいて実装されます。AQS はテンプレート メソッド パターンに基づいて設計されているため、ロックの実装は AQS を継承し、指定されたメソッドを書き換える必要があります。AQS は、FIFO キューを内部的に定義してスレッド同期を実現し、同期状態を定義してロック情報を記録します。

ボーナス回答

ReentrantLock は、内部クラス Sync を介してロックを定義し、Sync の 2 つのサブクラスである FrSync と NonfrSync も定義します。これらは、それぞれ公平なロックと不公平なロックを表します。Sync は AQS から継承されており、AQS の同期状態を使用してロック情報を記録するだけでなく、同期状態を使用して再入力回数を記録します。同期状態は整数で、0 の場合はロックなし、N の場合はスレッドがロックを保持して N 回再入することを意味します。ReentrantReadWriteLock は、ReentrantLock と同じ方法で再入可能をサポートします。また、内部クラス Sync を定義し、FrSync と NonfrSync の 2 つのサブクラスを定義して、公平なロックと不公平なロックを実装します。さらに、ReentrantReadWriteLock には、読み取りと書き込み用の 2 つのロックが内部的に含まれており、どちらも Sync によって実装されます。違いは、読み取りロックは共有をサポートすることです。つまり、複数のスレッドが読み取りロックを同時に正常に追加できますが、書き込みロックは相互に排他的です。つまり、ロックを正常に追加できるのは 1 つのスレッドだけです。

9. volatile の使い方と原理について話す

得点ポイント

機能、メモリ セマンティクス、実装メカニズム

特性:

順序性: volatile で宣言された変数の前のコードはその前に実行する必要があり、その後のコードはそれよりも遅く実行する必要があります。

Visibility: Once a variable is modified, it is immediately refreshed to the shared memory. 他のスレッドがこの変数を読み取りたい場合、最終的には自分のワークスペースではなくメモリから読み取ります。 

アトミック性:単一の volatile 変数の読み取りと書き込みはアトミックですが、「volatile 変数 ++」の複合操作はアトミックではありません。

注: volatile のアトミック性は単一の変数に対してのみであり、複合操作はアトミック性を保証できません。これは同期との違いでもあります。 

メモリのセマンティクス:

- 書き込みメモリ セマンティクス: 揮発性変数を書き込む場合、JMM (Java メモリ モデル) は、スレッドのローカル メモリ内の共有変数の値をメイン メモリに更新します。

- メモリ セマンティクスの読み取り: 揮発性変数を読み取る場合、JMM はスレッド ローカル メモリを無効にし、メイン メモリから共有変数を読み取らせます。

基礎となる実装メカニズム:

volatile の最下層は、メモリ バリアを使用して実装されます。つまり、コンパイラがバイトコードを生成するときに、メモリ バリアが命令シーケンスに挿入され、特定の種類のプロセッサの並べ替えが禁止されます

Volatile は軽量の同期であり、マルチプロセッサ開発における共有変数の「可視性」を保証します。可視性とは、あるスレッドが共有変数を変更すると、別のスレッドが変更された値を読み取ることができることを意味します。volatile を適切に使用すると、スレッド コンテキストの切り替えやスケジューリングが発生しないため、synchronized よりも実行コストが低くなります。つまり、volatile 変数には次のプロパティがあります。

- 可視性: volatile 変数を読み取るとき、いつでも (任意のスレッドで) この volatile 変数への最後の書き込みを確認できます。

- アトミック性: 単一の volatile 変数の読み取りと書き込みはアトミックですが、「volatile 変数 ++」の複合操作はアトミックではありません。

メモリのセマンティクス:

Volatile は、スレッドのメモリ可視性に影響を与えることによって上記の特性を実装します。また、次のメモリ セマンティクスがあります。その中で、JMM は Java メモリ モデルを指し、ローカル メモリは JMM の単なる抽象的な概念であり、キャッシュ、書き込みバッファ、レジスタ、およびその他のハードウェアとコンパイラの最適化をカバーします。この記事では、キャッシュとして単純に理解できます。

- 書き込みメモリ セマンティクス: 揮発性変数を書き込む場合、JMM はスレッドのローカル メモリ内の共有変数の値をメイン メモリに更新します。

- メモリ セマンティクスの読み取り: 揮発性変数を読み取る場合、JMM はスレッド ローカル メモリを無効にし、メイン メモリから共有変数を読み取らせます。

基礎となる実装メカニズム:

volatile の最下層は、メモリ バリアを使用して実装されます。つまり、コンパイラがバイトコードを生成するときに、メモリ バリアが命令シーケンスに挿入され、特定の種類のプロセッサの並べ替えが禁止されます。メモリ バリアは、プラットフォーム関連のコードの一部です. Java のメモリ バリア コー​​ドは、LoadFence()、storeFence()、および fullFence() の 3 つのメソッドを含む Unsafe クラスで定義されます。追加点の回答 メモリ セマンティクスの観点から、揮発性の読み取り/書き込みは、ロックの取得/解放と同じメモリ効果を持ちます。つまり、揮発性読み取りはロック取得と同じメモリ セマンティクスを持ち、揮発性書き込みはロック リリースと同じメモリ セマンティクスを持ちます。Volatile は単一の変数の読み取りと書き込みの原子性のみを保証できますが、ロックはクリティカル セクション全体のコード実行の原子性を保証できます。したがって、関数ロックは揮発性よりも強力であり、揮発性はスケーラビリティとパフォーマンスの点でより多くの利点があります。

10. スレッドの 6 つの状態について話す

得点ポイント

NEW、RUNNABLE、BLOCKED、WTING、TIMED_WTING、TERMINATED

標準的な答え

Java スレッドの実行中のライフサイクル中は、いつでも次の 6 つの状態のいずれかになります。

NEW: 初期状態では、スレッドは作成されていますが、start メソッドはまだ呼び出されていません。

RUNNABLE: 実行可能な状態、準備完了または実行中。スレッドは JVM で実行されていますが、オペレーティング システムのスケジューリングを待機している可能性があります。

BLOCKED: ブロック状態。スレッドはモニター ロックの取得を待機しています。

WTING: 待機状態。スレッドは他のスレッドからの通知または割り込みを待っています。

TIMED_WTING: タイムアウト待機状態。WTING に基づいてタイムアウト期間が追加されます。つまり、タイムアウトは自動的に返されます。

TERMINATED: 終了状態、スレッドは実行されました。

スレッドが作成されると、デフォルトで初期状態になり、start メソッドを呼び出した後に実行可能な状態になります. 実行可能な状態は、スレッドが実行されていることを意味するものではなく、オペレーティング システムのスケジューリングを待っている可能性があります. 待機状態に入ったスレッドが実行可能状態に戻るには、他のスレッドからの通知が必要であり、タイムアウト待機状態は、待機状態に基づいてタイムアウト制限を追加することに相当します。また、タイムアウト時間に達すると実行状態に戻ります。

また、スレッドが同期メソッドを実行すると、ロックを取得せずにブロック状態になります。スレッドが run メソッドを実行すると、終了状態になります。

ボーナス回答

Java は、オペレーティング システムでの準備完了と実行中の2 つの状態を組み合わせて、実行可能な状態(RUNNABLE) にします。スレッドが同期監視ロックによってブロックされると、スレッドはブロック状態に入りますが、スレッドがロック ロックによってブロックされると、スレッドは待機状態になります. これは、Lock インターフェース実装クラスが、関連するメソッドを使用するためです。ブロッキングを実装するための LockSupport クラス。

11. ThreadLocal についてのあなたの理解について話してください

得点ポイント

機能・仕組み

標準的な答え

機能:スレッド変数である ThreadLocal は同時にアクセスする必要がある複数のリソースをコピーして、各スレッドがリソースを持つようにします。各スレッドにはリソースの独自のコピーがあるため、この変数を同期する必要はありません。マルチスレッド コードを記述する場合、安全でない変数を ThreadLocal にカプセル化できます

メモリ リークの問題:スレッド プールを使用する場合、ThreadLocal は使用後にスレッド内のスレッド変数を手動で削除する必要があります。これは、スレッドがスレッド プール内で破棄されず、ThreadLocal 内のオブジェクトを自動的にガベージ コレクションできないためです。 

実装メカニズム:

実装に関しては、threadLocals 変数は Thread クラスで宣言され、現在のスレッド専用のリソースを格納するために使用されます。変数の型 (ThreadLocalMap) は、キーと値のペアを格納するMap のような構造である ThreadLocal クラスで定義されます。

The set and get methods are also provided in the ThreadLocal class. set メソッドは、ThreadLocalMap を初期化し、それを Thread.threadLocals にバインドして、現在のスレッドに着信値をバインドします。データ ストレージでは、着信値がキーと値のペアの値として使用され、キーは ThreadLocal オブジェクト自体 (this) です。get メソッドにはパラメーターがなく、現在の ThreadLocal オブジェクト (this) をキーとして使用して、現在のスレッドにバインドされたデータを Thread.threadLocals から取得します。

おまけの答え - ThreadLocal と同期メカニズムの比較

ThreadLocal は同期メカニズムに取って代わることはできず、この 2 つは異なる問題領域に直面していること に注意してください。

同期メカニズムは、同じリソースへの複数のスレッドの同時アクセスを同期することであり、複数のスレッド間で通信する効果的な方法です。スレッドがメモリ上で動作している場合、他のスレッドはこのメモリ アドレスで動作できません。

そして、ThreadLocalは複数のスレッドのデータ共有を分離し、複数のスレッド間の共有リソース (変数) の競合を根本的に回避するため、複数のスレッドを同期する必要はありません。

一般に、スレッド間の通信機能を実現するために複数のスレッドがリソースを共有する必要がある場合は、同期メカニズムが使用されます。複数のスレッド間の共有の競合を分離する必要がある場合は、ThreadLocal を使用できます。

ThreadLocal を使用したスレッド分離の例:

2 つのスレッド サブテーブルは、それぞれのスレッドに格納されている変数を取得します。それらの間の変数の取得は混同されません。

public class ThreadLocaDemo {
 
    private static ThreadLocal<String> localVar = new ThreadLocal<String>();
 
    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地内存中的本地变量
        localVar.remove();
    }
    public static void main(String[] args) throws InterruptedException {
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_A");
                print("A");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
               
            }
        },"A").start();
 
        Thread.sleep(1000);
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_B");
                print("B");
                System.out.println("after remove : " + localVar.get());
              
            }
        },"B").start();
    }
}
 
A :local_A
after remove : null
B :local_B
after remove : null
 

12. 知っているスレッド通信方式について話す

得点ポイント

モニター、状態

標準的な答え

スレッド通信:複数のスレッドが同時に実行されるとき, それらはランダムに切り替えられて CPU 内で実行されます. このとき, 複数のスレッドが一緒にタスクを完了したい. このとき, スレッド間の通信が必要です. スレッドは連携して作業を完了します.タスク。

スレッドの通信方法:  Monitor (同期モニター) と Condition。

スレッドの通信方法は、スレッドの同期方法に依存します。

synchronizeを使用して同期する場合は、 monitorを使用してスレッド通信を実現します. ここでの monitor は、実際にはlock オブジェクトであり、オブジェクトの wait 、 notify 、 notifyAll などのメソッドを使用してスレッド通信を実現します。

同期にLockを使用する場合は、 Conditionを使用してスレッド通信を実現することになります. Condition オブジェクトは Lock によって作成され、Lock オブジェクトに依存し、その await、sign または signAll メソッドを使用してスレッド通信を実現します。 

Java では、一般的に使用されるスレッド通信方法として、Monitor を使用してスレッド通信を実現する方法と、Condition を使用してスレッド通信を実現する方法の 2 つがあります。

スレッド同期はスレッド通信の前提であるため、どの方法で通信するかはスレッド同期の方法に依存します。

モニター

同期にsynchronizedキーワードを使用する場合は、 Monitor (同期モニター)に依存する必要があります。

スレッド通信を実現するために、Monitor はロック オブジェクトです。同期同期モードでは、ロック オブジェクトを任意のタイプにすることができるため、通信メソッドは Object クラスで自然に定義されます。これらのメソッドには、wt()、notify()、notifyAll() が含まれます。

スレッドが Monitor を介して wt() を呼び出すと、ロックが解放され、ここで待機します。他のスレッドが Monitor を介して notify() を呼び出すと、ここで待機しているスレッドが起こされます。他のスレッドが Monitor を介して notifyAll() を呼び出すと、ここで待機しているすべてのスレッドが起動されます。

ロック

JDK 1.5 では Lock インターフェイスとその実装クラスが追加され、より柔軟な同期方法が提供されました。

同期にLockオブジェクトを使用する場合、スレッド通信を実装するために Condition オブジェクトに依存する必要があります. Condition オブジェクトは Lock オブジェクトによって作成され、Lock オブジェクトに依存します.

Condition オブジェクトで定義されている通信メソッドは、awt()、signal()、signalAll() など、Object クラスの通信メソッドと似ています。その意味は名前からわかります. Condition を通じて awt() を呼び出すと、現在のスレッドはロックを解放して待機します. Condition を通じて signal() を呼び出すと、待機中のスレッドを起動します. Condition を通じて signalAll() を呼び出すと、現在のスレッドはロックを解除します.待機中のすべてのスレッド。

ボーナス回答

スレッド同期は同期キューに基づいて実現され、スレッド通信は待機キューに基づいて実現されます。待機中のメソッドが呼び出されると、現在のスレッドが待機キューに追加されます。notify メソッドが呼び出されると、待機キュー内の 1 つ以上のスレッドが同期キューに戻されます。同期にはモニターが 1 つしかないため、待機キューは 1 つだけです。Lock オブジェクトは複数の条件を作成できるため、複数の待機キューがあります。複数の待機キューがあると柔軟性が高くなるため、Condition に基づいた通信方法がより推奨されます。たとえば、生産および消費モデルを実装する場合、生産者は消費者に通知し、消費者は生産者に通知する必要があります。逆に、生産者が生産者に通知し、消費者が消費者に通知するという状況があってはなりません。このモデルの実装に synchronized を使用すると、待機キューが 1 つしかないため、プロデューサーとコンシューマーを同じキューにしか追加できず、プロデューサーがプロデューサーに通知し、コンシューマーがコンシューマーに通知するという状況が発生します。Lock を使用してこのモデルを実装すると、待機キューが複数あるため、2 つの役割を効果的に分離して、このような問題を回避できます。

13. スレッドの作成方法について話す

得点ポイント

スレッド、実行可能、呼び出し可能

標準的な答え

スレッドを作成するには、次の 3 つの方法があります。

1. Thread クラスを継承し、run() メソッドを書き直します。

2. Runnable インターフェイスを実装し (推奨)、インターフェイスの run() メソッドを実装します。

3. Callable インターフェースを実装し、call() メソッドを書き直します。

最初の 2 つのメソッドには、スレッドの実行後に戻り値がなく、最後のメソッドには戻り値があります。

Java は単一継承であり、スレッド クラスはインターフェイスの実装中に他のクラスから継承できるため、Runnable インターフェイスを実装することをお勧めします。

スレッドを作成するには、Thread クラスを継承する方法、Runnable インターフェイスを実装する方法、および Callable インターフェイスを実装する方法の 3 つがあります。

1. Thread クラスを継承してスレッドを作成する手順は次のとおりです。

  • ・Threadクラスのサブクラスを定義し、このクラスのrun()メソッドを書き換えてスレッド実行本体とする。
  • - Thread サブクラスのインスタンスを作成します。つまり、スレッド オブジェクトを作成します。
  • - スレッド オブジェクトの start() メソッドを呼び出して、スレッドを開始します。

2. Runnable インターフェースを実装してスレッドを作成する手順は次のとおりです。

  • ・Runnableインターフェースの実装クラスを定義し、スレッド実行本体となるインターフェースのrun()メソッドを実装する。
  • - Runnable 実装クラスのインスタンスを作成し、それをパラメーターとして使用して、スレッド オブジェクトである Thread オブジェクトを作成します。
  • - スレッド オブジェクトの start() メソッドを呼び出して、スレッドを開始します。

3. Callable インターフェースを実装してスレッドを作成する手順は次のとおりです。

  • - Callable インターフェースの実装クラスを定義し、スレッドの実行本体となる call() メソッドを実装します。
  • - Callable 実装クラスのインスタンスを作成し、インスタンスをパラメータとして使用して FutureTask オブジェクトを作成します。
  • - FutureTask オブジェクトをパラメーターとして使用し、Thread オブジェクトを作成してから、スレッドを開始します。
  • - 子スレッドの実行が完了した後に、FutureTask オブジェクトの get() メソッドを呼び出して戻り値を取得します。

要約すると、実際にはスレッドを作成する方法は 2 つしかありません。親クラスを継承する方法と、インターフェイスを実装する方法です。

Runnable インターフェースとCallable インターフェースを使用する違いは、前者はスレッド実行端の戻り値を取得できず、後者はスレッド実行端の戻り値を取得できることです。

親クラスを継承してインターフェースを実装することの利点と欠点は次のとおりです。

  • -インターフェースを使用してスレッドを作成する利点は、スレッド クラスが他のクラスからも継承できること、および複数のスレッドがスレッド本体を共有できることです。これは、複数のスレッドが同じリソースを処理する状況に適しています欠点は、プログラミングが少し面倒なことです。
  • - 継承を使用してスレッドを作成すると、プログラミングが少し簡単になるという利点があります。欠点は、thread クラスが Thread クラスを継承しているため、他の親クラスを継承できないことです

したがって、通常の状況では、インターフェイス メソッドを使用してスレッドを作成することをお勧めします。値を返す必要がある場合は Callable インターフェイスを使用し、それ以外の場合は Runnable インターフェイスを使用します。

14. スレッドプールについてのあなたの理解について話してください

得点ポイント

コア パラメータ、処理フロー、拒否ポリシー、ライフ サイクル

標準的な答え

スレッド プールは、スレッドを効果的に管理できます。つまり、スレッドの数を管理し、スレッドを再利用できるようにします。

スレッド プールのライフ サイクルには、RUNNING、SHUTDOWN、STOP、TIDING、TERMINATED の 5 つの状態があります。これら 5 つの状態の状態値は、-1、0、1、2、3 です。スレッド プールのライフ サイクルでは、その状態は小さいものから大きいものにしか移行できず、元に戻すことはできません。

スレッド プールの 5 つの状態: 実行中、シャットダウン、停止、通知、終了。

スレッドの 6 つの状態: 新しい実行可能なブロックの待機中 timed_waiting 終了

スレッド プールは、スレッドを効率的に管理できます。

スレッドの数を管理でき、過度のシステム負荷やクラッシュの原因となるスレッドの無制限の作成を回避できます。

また、スレッドの再利用が可能になり、スレッドの作成と破棄によるオーバーヘッドを大幅に削減できます。

スレッド プールは、タスクの実行プロセスを制御するためにいくつかのパラメータに依存する必要があります. 最も重要なパラメータは次のとおりです: corePoolSize (コア スレッドの数), workQueue (待機キュー), maximumPoolSize (最大スレッド数), handler (拒否戦略) )、keepAliveTime (アイドル スレッドの生存時間)。スレッド プールにタスクを送信すると、スレッド プールは次のようにタスクを処理します。

1. スレッド数が corePoolSize に達しているかどうかを判断し、到達していない場合は新しいスレッドを作成してタスクを実行し、到達していない場合は次の手順に進みます。

2. 待機キューがいっぱいかどうかを判断し、そうでない場合はタスクを待機キューに入れ、そうでない場合は次のステップに進みます。

3. スレッドの数が maximumPoolSize に達しているかどうかを判断し、達していない場合は新しいスレッドを作成してタスクを実行し、そうでない場合は次のステップに進みます。

4. スレッド プールの初期化時に指定した拒否ポリシーを使用して、タスクの実行を拒否します。

5. 新しく作成されたスレッドが現在のタスクの処理を終了した後、すぐに閉じられることはありませんが、待機キュー内のタスクを処理し続けます。

スレッドのアイドル時間が keepAliveTime に達すると、スレッド プールはいくつかのスレッドを破棄し、スレッドの数を corePoolSize に縮小します。手順 2 のキューは、制限付きまたは制限なしにすることができます。無制限のキューが指定されている場合、スレッド プールはステップ 3 に入ることはありません。これは maximumPoolSize パラメータを破棄することと同じです。このような使い方は非常に危険で、キューに大量のタスクが溜まるとメモリオーバーフローを起こしやすくなります。JDK には Executors というスレッド プール作成ツールが用意されています. このツールは無制限のキュー (待機キュー内のタスクの数を制限しない) でスレッド プールを作成するため, 通常は作業にはお勧めしません. このクラスを使用して作成します.スレッドプール。ステップ 4 には、主に 4 つの拒否戦略があります。呼び出し元にタスクを実行させる、例外を直接スローする、処理せずにタスクを破棄する、キュー内の最も古いタスクを削除する、現在のタスクをキューに追加する、の 4 つです。これらの 4 つの拒否戦略は RejectedExecutionHandler インターフェースの 4 つの実装クラスに対応しており、このインターフェースに基づいて独自の拒否戦略を実装することもできます。Java では、スレッド プールの実際のタイプは ThreadPoolExecutor であり、スレッド プールの一般的な使用法を提供します。このクラスには、スケジュールされたタスクのサポートを提供する ScheduledThreadPoolExecutor という名前のサブクラスもあります。サブクラスでは、タスクを定期的に繰り返すか、タスクの実行を一定時間遅らせることができます。

ボーナス回答

スレッド プールのライフ サイクルには、RUNNING、SHUTDOWN、STOP、TIDING、TERMINATED の 5 つの状態があります。これら 5 つの状態の状態値は、-1、0、1、2、3 です。スレッド プールのライフ サイクルでは、その状態は小さいものから大きいものにしか移行できず、元に戻すことはできません。

1. RUNNING: スレッド プールが実行中であることを示します。

2. SHUTDOWN: shutdown() が実行されると、この状態に入ります. このとき、キューはクリアされず、スレッドプールはタスクの完了を待ちます.

3. STOP: shutdownNow() が実行されると、この状態に入ります. この時点で、現在のスレッドプールはキューをクリアし、タスクの実行を待機しなくなります.

4. TIDING :スレッド プールとキューが空である場合、この状態に入ります. このとき、スレッド プールはフック関数を実行します. 現在、この関数は空の実装です.

5. TERMINATED: フック関数が実行された後、スレッドはこの状態に入り、スレッド プールが停止したことを示します。

15. 知っているスレッドセーフなコレクションは何ですか?

得点ポイント

コレクション ツール クラス、java.util.concurrent (JUC)

標準的な答え

スレッドセーフなコレクションには次のものがあります。

  1. Collectionsツール クラスの synchronizedXxx() メソッドは、ArrayList などのコレクション クラスをスレッド セーフなコレクション クラスにラップします。
  2. Vector や Hashtable など、 java.util パッケージの下でパフォーマンスの低い古い API
  3. ConcurrentHashMap などのコンカレンシー パフォーマンスを向上させるためにロックの粒度を減らすために、JUC パッケージのConcurrentで始まるコンテナー。
  4. JUC パッケージのCopyOnWriteで始まり、CopyOnWriteArrayList などのコピーオンライト テクノロジによって実装される同時実行コンテナー。
  5. Lockによって実装されたブロッキング キューは、プロデューサーとコンシューマーをそれぞれ待機するために内部で 2 つの条件を作成します. これらのクラスはすべて、ArrayBlockingQueue などの BlockingQueue インターフェイスを実装します.

 

スレッド セーフ コレクション: コレクション ツール クラスと並行パッケージのコレクション クラス。

コレクション ツール クラス:このツール クラスによって提供される synchronizedXxx() メソッドは、これらのコレクション クラスをスレッド セーフなコレクション クラスにラップできます。 

並行パッケージのコレクション クラス: java5 以降、並行パッケージによって提供される並行アクセスをサポートする多数のコレクション クラス (ConcurrentHashMap/CopyOnWriteArrayList など) を使用できます。コンカレントは、同時、コンカレント、およびコンカレントとして変換されます

パフォーマンスの低い古い API: Vector、Hashtable。

java.util パッケージのほとんどのコレクション クラスはスレッド セーフではありませんが、Vector や Hashtable など、非常に古い API であるスレッド セーフなコレクション クラスもいくつかあります。これらはスレッドセーフですが、パフォーマンスが低く、使用は推奨されていません。For non-thread-safe collections under this package, you can use the Collections tool class. ツール クラスによって提供される synchronizedXxx() メソッドは、これらのコレクション クラスをスレッド セーフなコレクション クラスにパッケージ化できます。

JDK 1.5 から、concurrent パッケージの下に多数の効率的な並行コンテナーが追加されました。これらのコンテナーは、実装メカニズムに従って 3 つのカテゴリーに分けることができます。

最初のカテゴリは、ロックの粒度を減らしてコンテナーの同時実行パフォーマンスを向上させることであり、それらのクラス名は ConcurrentHashMap などの Concurrent で始まります。

2 番目のタイプは、コピー オン ライト テクノロジによって実装される同時実行コンテナーであり、そのクラス名は CopyOnWriteArrayList などの CopyOnWrite で始まります。

3 番目のカテゴリは、Lock によって実装されるブロッキング キューです. プロデューサーとコンシューマーをそれぞれ待機させるために、2 つの条件が内部的に作成されます. これらのクラスはすべて、ArrayBlockingQueue などの BlockingQueue インターフェイスを実装します.

ボーナス回答

Collections also provides the following three types of methods to return an immutable collection. これら 3 種類のメソッドのパラメーターは元のコレクション オブジェクトであり、戻り値はコレクションの "読み取り専用" バージョンです。コレクションが提供する 3 種類のメソッドを使用して、「読み取り専用」のコレクションまたはマップを生成できます。

emptyXxx(): 空の不変コレクション オブジェクトを返します

singletonXxx(): 指定されたオブジェクトのみを含む不変のコレクション オブジェクトを返します unmodifiableXxx(): 指定されたコレクション オブジェクトの不変のビューを返します 

16. HashMap はスレッドセーフですか? そうでない場合、それを解決する方法は?

得点ポイント

Hashtable、Collections、ConcurrentHashMap

標準的な答え

HashMap はスレッドセーフであり、基盤となる実装は「配列、連結リスト、赤黒木」です。マルチスレッドの put の場合、データが上書きされる可能性があり、put は modCount++ 操作を実行します。このステップは、読み取り、増加、および増加に分けられます。 and save. アトミック操作。

解決:

  1. ハッシュテーブルの使用 (古代)
  2. Collections ツール クラスを使用して、HashMap をスレッド セーフな HashMap にラップします。
  3. より安全な CurrentHashMap (推奨)を使用してください

マルチスレッド環境では、複数のスレッドが同時に HashMap の変更をトリガーすると、競合が発生する可能性があります。したがって、マルチスレッド環境で HashMap を使用することはお勧めしません。

There are three ways to use a thread-safe HashMap: use Hashtable, use Collections to package HashMap into a thread-safe HashMap, and use ConcurrentHashMap. 3 番目の方法が最も効率的であり、最も推奨される方法です。

ハッシュ表

HashMap と Hashtable はどちらも典型的な Map 実装であり、Hashtable はスレッドセーフです。これはオプションですが、お勧めしません。Hashtable はJava 1.0 から登場した古代の APIであるため、同期スキームが未熟であり、パフォーマンスが良くなく、公式のアドバイスでさえ推奨されていません。

コレクション

SynchronizedMap() メソッドはCollections クラスで提供され渡した Map をスレッド同期の Map にラップできます。さらに、Collections には、不変コレクションを返す次の 3 種類のメソッドも用意されています. これら 3 種類のメソッドのパラメーターは元のコレクション オブジェクトであり、戻り値はコレクションの "読み取り専用" バージョンです. コレクションが提供する 3 種類のメソッドを使用して、「読み取り専用」マップを生成できます。emptyMap(): 空の不変 Map オブジェクトを返します。singletonMap(): 指定されたキーと値のペアのみを含む不変の Map オブジェクトを返します。unmodifiableMap() : 指定された Map オブジェクトの不変ビューを返します。

コンカレントハッシュマップ

ConcurrentHashMap はスレッドセーフで効率的な HashMap であり、JDK 8 でアップグレードされました。これにより、JDK 7 に基づいてロックの粒度がさらに減少し、それによって並行性の能力が向上します。JDK 7 では、ConcurrentHashMap の基礎となるデータ構造は「配列 + リンクされたリスト」ですが、ロックの粒度を減らすために、JDK7 はマップをいくつかのサブマップに分割し、各サブマップはセグメントと呼ばれます。複数のセグメントは互いに独立しており、各セグメントには複数のスロットが含まれており、セグメント内のデータが衝突すると、リンクされたリスト構造を使用して解決されます。データを同時に挿入する場合、ConcurrentHashMap はマップ全体ではなく、セグメントをロックします。ロックの粒度がセグメントであるため、このモードは「セグメント化されたロック」とも呼ばれます。さらに、セグメントはコンテナーの初期化時に決定され、後で変更することはできません。各セグメントは独立して拡張でき、各セグメントは互いに影響を与えないため、同時拡張の問題はありません。JDK8では、ConcurrentHashMapの基になるデータ構造は「配列+連結リスト+赤黒木」ですが、JDK8ではさらにロックの粒度を下げるために、セグメントの設定を解除し、連結リストまたは赤黒木を格納します。マップのスロットに直接ツリーを配置します。同時に挿入する場合は、先頭ノードをロックします. セグメントの先頭ノードの数と比較して、ノードの数は拡張によって増加できるため、粒度は小さくなります. 赤黒ツリーは、競合が深刻な場合にスロット内の要素を見つける効率を向上させるために導入されています。

17. JUCについて教えてください

得点ポイント

アトミック クラス、ロック、スレッド プール、並行コンテナー、同期ツール

標準的な答え

JUC は java.util.concurrent の略で、並行操作をサポートする各種ツールをパッケージ化したものです。

1. アトミック クラス: JUC.Atomic パッケージの下で、CAS (比較と置換) の原則に従って、多くのアトミック操作メソッドを提供します。単一の変数のスレッド セーフ問題を解決するために使用できます

2.Lock ロック: Synchronized と同様に、Synchronized のすべての機能を含むことに基づいて、タイムアウト メカニズムと応答割り込みメカニズムもサポートします。これは、主に複数の変数のスレッド セーフ問題を解決するために使用されます。

3. スレッドプール:スレッドをより便利に管理できると同時に、スレッドのオープンとキルの繰り返しによる消費を高効率で回避できます。

4. 並行コンテナー:マルチスレッド操作の並行コレクションをサポートし、より効率的な ConcurrentHashMap など。

JUC はjava.util.concurrentの略です. このパッケージは JDK 1.5 が提供する並行パッケージです. 主に並行操作をサポートする各種ツールを提供するパッケージです. これらのツールは、アトミック クラス、ロック、スレッド プール、同時実行コンテナー、および同期ツールの 5 つのカテゴリに大別されます。

 1. アトミック クラスJDK 1.5 から、コンカレント パッケージの下にアトミック サブパッケージが提供されます. このパッケージのアトミック操作クラスは、簡単な使用法、高性能、およびスレッド セーフで変数を更新する方法を提供します。アトミック パッケージには、アトミック更新基本型、アトミック更新参照型、アトミック更新属性、アトミック更新配列の 4 種類のアトミック更新メソッドに属する、合計 17 個のクラスが用意されています。

アトミック クラスの場合、内部に多数のアトミック操作メソッドが用意されています。整数値のアトミック置換、指定値の増加、1 の加算など、これらのメソッドの最下層は、オペレーティング システムによって提供されるCAS アトミック命令によって実現されます単一の変数のスレッド セーフ問題を解決するために使用できます

AtomicInteger を例にとると、スレッドがオブジェクトの incrementAndGet() メソッドを呼び出すと、CAS を使用してその値を変更しようとします。この時点で他のスレッドが値を操作しない場合、値は正常に変更されます。それ以外の場合、CAS は変更が成功するまで操作が繰り返し実行されます。

 2. JDK 1.5 から、コンカレント パッケージに Lock インターフェイスと関連する実装クラスが追加され、ロック機能が実装されます.これは、synchronized キーワードと同様の同期機能を提供しますが、使用時に明示的に取得および解放する必要があります。 . 暗黙的にロックを取得および解放するという便利さはありませんが、synchronized キーワードにはないさまざまな同期機能があります。たとえば、中断可能なロックの取得、ロックの非ブロック取得、ロックのタイムアウト取得などがあります。

 3. スレッドプール JDK 1.5から、concurrent パッケージの下に組み込みのスレッドプールが追加されました。その中で、ThreadPoolExecutor クラスは従来のスレッド プールを表し、そのサブクラスである ScheduledThreadPoolExecutor はタイミング タスクのサポートを提供します. サブクラスでは、タスクを定期的に繰り返したり、タスクを実行する前に特定の時間を遅らせたりすることができます. また、Executors はスレッドプールを作成するためのツールクラスですが、このクラスは無制限のキューを持つスレッドプールを作成するため、使用する際には注意してください。

 4. 並行コンテナーJDK 1.5 から、多数の効率的な並行コンテナーが並行パッケージの下に追加されました. これらのコンテナーは、実装メカニズムに従って 3 つのカテゴリに分類できます。最初のカテゴリは、ロックの粒度を減らしてコンテナーの同時実行パフォーマンスを向上させることであり、それらのクラス名は ConcurrentHashMap などの Concurrent で始まります。2 番目のタイプは、コピー オン ライト テクノロジによって実装される同時実行コンテナーであり、そのクラス名は CopyOnWriteArrayList などの CopyOnWrite で始まります。3 番目のカテゴリは、Lock によって実装されるブロッキング キューです. プロデューサーとコンシューマーをそれぞれ待機させるために、2 つの条件が内部的に作成されます. これらのクラスはすべて、ArrayBlockingQueue などの BlockingQueue インターフェイスを実装します.

 5. 同期ツール JDK 1.5から、いくつかの便利な同時実行ツール クラスが同時実行パッケージの下に追加されました。これにより、スレッドの安全性も確保できます。その中で、Semaphore クラスは、特定のリソースに同時にアクセスするスレッドの数を制御できるセマフォを表します; CountDownLatch クラスは、1 つ以上のスレッドが他のスレッドが操作を完了するのを待つことを可能にします; CyclicBarrier は、スレッドのグループを可能にします最後のスレッドまで、バリアに到達するとブロックされます。バリアに到達すると、バリアが開かれ、バリアによってブロックされたすべてのスレッドが引き続き実行されます。

18. ConcurrentHashMap について教えてください

得点ポイント

配列 + リンク リスト + 赤黒木、ロック粒度

標準的な答え 

並行パッケージのコレクション クラスはスレッド セーフです。

ConcurrentHashMap は、JUC ( java.util.concurrent )同時実行パッケージの下にあるクラスであり、スレッドセーフな HashMap と同等です。 

配列 + 連結リスト + 赤黒木: ConcurrentHashMap の基になるデータ構造は HashMap と同じで、「配列 + 連結リスト + 赤黒木」も使用します。

ロックの粒度:ヘッド ノードをロックする方法は、ロックの粒度を減らし、より低いパフォーマンス コストでスレッド セーフを実現します。

実装メカニズム:

1.配列またはヘッドノードを初期化するとき、ConcurrentHashMap はロックされませんが、CASモードでアトミック置換を実行します

2. ハッシュ ルックアップは、データの挿入時にロックを実行しますが、ロックされるのは配列全体ではなく、スロット内の連結リストの先頭ノードですしたがって、ConcurrentHashMap のロックの粒度は配列全体ではなくスロットであり、同時実行パフォーマンスは非常に優れています。

3.チェーン アドレス方式が競合を処理して容量を拡張するとロック処理が実行され、ロックはまだヘッド ノードですさらに、複数のスレッドをサポートして配列を同時に拡張し、並行性を向上させます。

4. 拡張の過程で、検索操作は引き続きサポートされます。

基礎となるデータ構造のロジックは、HashMap の実装を参照できます. 以下では、そのスレッドセーフな実装メカニズムに焦点を当てます.

1.配列やヘッドノードの初期化時、ConcurrentHashMapはロックせず、CAS(compare and exchange)モードでアトミック置換(アトミック操作、Unsafeクラスに基づくアトミック操作API)を行います。

CAS、コンペア アンド スワップは、マルチスレッド並列処理の場合にロックの使用によって引き起こされるパフォーマンスの低下を解決するメカニズムです。

CAS 演算は、メモリ位置 (V)、期待される古い値 (A)、および新しい値 (B) の 3 つのオペランドで構成されます。メモリ位置の値が予想される元の値と一致する場合、プロセッサはその位置の値を新しい値で自動的に更新します。それ以外の場合、プロセッサは何もしません。いずれの場合も、CAS 命令の前のその位置の値を返します。CAS は事実上、「場所 V には値 A が含まれているはずだと思います。もしそうなら、この場所に B を置きます。そうでない場合は、その場所を変更しないでください。この場所が現在何であるかを教えてください。

2. データ挿入時にロック処理が行われますが、ロックは配列全体ではなく、スロット内の先頭ノードです。したがって、ConcurrentHashMap のロックの粒度は配列全体ではなくスロットであり、同時実行のパフォーマンスは非常に優れています。

3. 拡張中にロックが実行され、ヘッド ノードは引き続きロックされます。さらに、複数のスレッドをサポートして配列を同時に拡張し、並行性を向上させます。各スレッドは、最初に CAS 操作を使用してタスクを取得し、連続スロットのデータ転送権をめぐって競合する必要があります。タスクを取得した後、スレッドはスロット内のヘッド ノードをロックし、リンクされたリストまたはツリー内のデータを新しい配列に移行します。

4. データの検索時にロックがないため、パフォーマンスが非常に優れています。さらに、拡張の過程で、検索操作は引き続きサポートされます。スロットが移行されていない場合、データは古いアレイから直接見つけることができます。スロットが移行されたが、拡張全体が終了していない場合、拡張スレッドは転送ノードを作成し、それを古い配列に格納します。その後、検索スレッドは、次のプロンプトに従って、新しい配列からターゲット データを見つけます。転送ノード。

ボーナス回答

ConcurrentHashMap がスレッド セーフを実現するのが難しいのは、複数のスレッドが同時に展開される場合です. つまり、スレッドがデータを挿入しているときに、配列が展開されていることを検出すると、すぐに展開操作に参加し、データを挿入します.拡張が完了したら、新しいアレイに挿入します。展開中、複数のスレッドがデータ移行タスクを共有し、各スレッドが担当する移行の数は、「(配列の長さ >>> 3) / CPU コアの数」です。つまり、スレッドに割り当てられる移行タスクは、ハードウェアの処理能力を十分に考慮します。ハードウェアの処理能力に応じて、複数のスレッドがスロット移行作業の一部を均等に分担します。また、計算されたマイグレーション数が 16 未満の場合は、強制的に 16 に変更されます。これは、現在のサーバー分野で主流の CPU 動作速度を考慮すると、毎回処理されるタスクが少なすぎるためです。 CPU の計算能力の浪費。 

おすすめ

転載: blog.csdn.net/qq_40991313/article/details/129446871