【2023年】Javaマルチスレッドスレッドプール解説まとめ(コード例含む)

1. JAVA がスレッド プールを使用する 3 つの主な理由

  1. スレッドの作成/破棄はシステム リソースを消費しますが、スレッド プールは作成されたスレッドを再利用できます。
  2. 同時実行の量を制御します同時実行性が過剰になると、リソースが過剰に消費され、サーバーがクラッシュする可能性があります。(主な理由)
  3. ラインの一元管理が可能。

スレッド プールを作成するには合計 7 つの方法があり、通常は次の 2 つのカテゴリに分類されます。

1. ThreadPoolExecutor によって作成されたスレッド プール。

2. エグゼキュータによって作成されたスレッド プール。

2. スレッド プールを作成する方法:

  • Executors.newFixedThreadPool: 固定サイズのスレッド プールを作成します。これにより同時スレッドの数を制御でき、過剰なスレッドはキュー内で待機します。
  • Executors.newCachedThreadPool: キャッシュ可能なスレッド プールを作成します。スレッドの数が処理要件を超える場合、キャッシュは一定期間後にリサイクルされます。スレッドの数が十分でない場合は、新しいスレッドが作成されます。
  • Executors.newSingleThreadExecutor: 単一のスレッド数でスレッド プールを作成します。これにより、先入れ先出しの実行順序が保証されます。
  • Executors.newScheduledThreadPool: 遅延タスクを実行できるスレッド プールを作成します。
  • Executors.newSingleThreadScheduledExecutor: 遅延タスクを実行できるシングルスレッド スレッド プールを作成します。
  • Executors.newWorkStealingPool: プリエンプティブ実行用のスレッド プールを作成します (タスクの実行順序は不定です) [JDK1.8 追加]。
  • ThreadPoolExecutor: スレッド プールを作成する最も原始的な方法で、設定用の 7 つのパラメーターが含まれています。これについては後で詳しく説明します。

タスクの追加方法:
* 戻り値の有無にかかわらずタスクを実行するには submit を使用しますが、execute は戻り値のないタスクのみを実行できます。*****

スレッド ファクトリを使用してスレッドを作成します。

ここに画像の説明を挿入します

提供される機能:

1. (スレッド プール内の) スレッドの命名規則を設定します。

2. スレッドの優先順位を設定します。

3. スレッドのグループ化を設定します。

4. スレッドの種類(ユーザースレッド、デーモンスレッド)を設定します。

3. スレッドプールを作成する

1. 固定サイズのスレッド プールを作成する
ここに画像の説明を挿入します

2. キャッシュ可能なスレッド プールを作成し、作成されたスレッド タスクの数を確認します。

  1. 利点: これらのスレッドは、一定期間内で再利用して、対応するスレッド プールを生成できます。
  2. 短所: 短期間に大量のタスクを実行するシナリオに適していますが、多くのリソースを占有する可能性があることが短所です。
    ここに画像の説明を挿入します

3. 遅延スケジュールされたタスクのスレッド プールを作成します
。 3.scheduleAtFixedRateは、前のタスクの開始時刻であり、次のスケジュールされたタスクの基準時間として使用されます (基準時間 + 遅延タスク = タスクの実行)。*
4.scheduleWithFixedDelayは、前のタスクの終了時刻であり、次にスケジュールされたタスクの基準時刻として使用されます。*
5.schedule(): 遅延時間を設定し、定期的に実行できます;
ここに画像の説明を挿入します
4. シングルスレッド スレッド プールの作成
ここに画像の説明を挿入します
5. プリエンプティブル スレッド プールの作成
ここに画像の説明を挿入します

⭐6. ThreadPoolExecutor を使用して
ThreadPoolExecutor を作成する パラメータの説明:
ここに画像の説明を挿入します
拒否ポリシー

  • JDK 4 種類 + 1 つのカスタム戦略
    ここに画像の説明を挿入します
    固有のコード
    ここに画像の説明を挿入します

実装プロセス

  1. スレッドの合計数 < corePoolSize の場合、スレッドがアイドル状態であるかどうかに関係なく、タスクを実行するために新しいコア スレッドが作成されます (コア スレッドの数 < corePoolSize の場合、コア スレッドの数がすぐに corePoolSize に達します)
    この手順ではグローバル ロックを取得する必要があることに注意してください。
  2. スレッドの総数 >= corePoolSize の場合、新しいスレッド タスクはタスク キューに入って待機し、その後、アイドル状態のコア スレッドが実行のためにキャッシュ キューからタスクを順番にフェッチします (スレッドの再利用を反映)
  3. キャッシュ キューがいっぱいになると、その時点でタスクが多すぎることを意味し、これらのタスクを実行するには「一時的なワーカー」が必要になります。したがって、このタスクを実行するために非コア スレッドが作成されます。この手順にはグローバル ロックが必要であることに注意してください。
  4. キャッシュ キューがいっぱいで、スレッドの総数が MaximumPoolSize に達すると、上記の拒否戦略が処理に採用されます。
    ここに画像の説明を挿入します

ブロックキュー

プロデューサがリソースを生成し続け、コンシューマがリソースを消費し続けるシナリオを想定します。リソースはバッファ プールに格納されます。プロデューサは、生成されたリソースをバッファ プールに格納します。コンシューマは、消費のためにバッファ プールからリソースを取得します。これは、有名な生産者兼消費者モデル

このパターンは開発プロセスを簡素化できます。一方で、プロデューサ クラスとコンシューマ クラスの間のコードの依存関係が排除され、他方で、データを生成するプロセスとデータを使用するプロセスが切り離されて、負荷が簡素化されます。

このパターンを自分で実装するコードを作成する場合、複数のスレッドが共有変数 (つまり、リソース) を操作する必要があるため、特に複数のプロデューサーとコンシューマーが存在する場合、スレッドの安全性の問題が発生しやすく繰り返しの消費デッドロックが発生します。さらに、バッファ プールが空の場合は、コンシューマをブロックしてプロデューサーをウェイクアップする必要があり、バッファ プールがいっぱいの場合は、プロデューサーをブロックしてコンシューマをウェイクアップする必要があります。これらの待機/ウェイクアップ ロジックは次のようにする必要があります。自分たちで実装しました。

BlockingQueue は、Java util.concurrent パッケージの重要なデータ構造です。通常のキューとは異なり、BlockingQueue は、スレッドセーフなキュー アクセス メソッドを提供します。concurrent パッケージの多くの高度な同期クラスの実装は、BlockingQueue に基づいています。

BlockingQueue は通常、プロデューサー/コンシューマー モデルで使用され、プロデューサーはキューに要素を追加するスレッド、コンシューマーはキューから要素を取得するスレッドです。BlockingQueue は要素を格納するためのコンテナです

ブロッキング キューは、要素を挿入、削除、検査するための 4 つの異なるメソッド セットを提供します。

メソッド\処理メソッド 例外をスローする 特殊な値を返す 常にブロックされています タイムアウトで終了
挿入メソッド 追加(e) オファー(e) 置く(e) オファー(e、時間、単位)
除去方法 取り除く() ポーリング() 取る() 投票(時間、単位)
検査方法 要素() ピーク() - -

ブロッキングキュー: BlockingQueue workQueue

  • 実行を待機している Runnable タスク オブジェクトを維持します。
  1. LinkedBlockingQueue
    1. チェーンされたブロッキング キュー、基礎となるデータ構造はリンク リスト、デフォルト サイズは Integer.MAX_VALUE、サイズも指定されます
  2. 配列ブロックキュー
    1. 配列ブロックキュー。基礎となるデータ構造は配列であり、キューのサイズを指定する必要があります。
  3. 同期キュー
    1. 同期キュー、内部容量は 0、各 put 操作は take 操作を待機する必要があり、その逆も同様です。
  4. 遅延キュー
    1. 遅延キュー。キュー内の要素は、指定された遅延時間が経過したときにのみキューから取得できます。
  5. 優先ブロッキングキュー
    1. 無制限のブロッキング キューの場合、優先度はコンストラクターによって渡される Compator オブジェクトによって決定されます。

キューをブロックする原理

ブロッキングキューは主に、Lock ロックの複数条件 (Condition) ブロッキング制御を使用します。

1 つ目はコンストラクターで、キューのサイズと公平なロックかどうかの初期化に加えて、同じロック (ロック) に対する 2 つのモニター (notEmpty と notFull) も初期化します。これら 2 つのモニターの機能は、現時点では単にマーク グループ化として理解できます。スレッドが put 操作を実行しているときは、モニター notFull をそれに追加してスレッドをプロデューサーとしてマークし、スレッドが take 操作を実行しているときは、モニターを追加します。 notEmpty を監視し、このスレッドをコンシューマとしてマークします。

おすすめ

転載: blog.csdn.net/weixin_52315708/article/details/131521785