1. JAVA がスレッド プールを使用する 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. キャッシュ可能なスレッド プールを作成し、作成されたスレッド タスクの数を確認します。
- 利点: これらのスレッドは、一定期間内で再利用して、対応するスレッド プールを生成できます。
- 短所: 短期間に大量のタスクを実行するシナリオに適していますが、多くのリソースを占有する可能性があることが短所です。
3. 遅延スケジュールされたタスクのスレッド プールを作成します
。 3.scheduleAtFixedRateは、前のタスクの開始時刻であり、次のスケジュールされたタスクの基準時間として使用されます (基準時間 + 遅延タスク = タスクの実行)。*
4.scheduleWithFixedDelayは、前のタスクの終了時刻であり、次にスケジュールされたタスクの基準時刻として使用されます。*
5.schedule(): 遅延時間を設定し、定期的に実行できます;
4. シングルスレッド スレッド プールの作成
5. プリエンプティブル スレッド プールの作成
⭐6. ThreadPoolExecutor を使用して
ThreadPoolExecutor を作成する パラメータの説明:
拒否ポリシー
- JDK 4 種類 + 1 つのカスタム戦略
固有のコード
実装プロセス
- スレッドの合計数 < corePoolSize の場合、スレッドがアイドル状態であるかどうかに関係なく、タスクを実行するために新しいコア スレッドが作成されます (コア スレッドの数 < corePoolSize の場合、コア スレッドの数がすぐに corePoolSize に達します)
。この手順ではグローバル ロックを取得する必要があることに注意してください。- スレッドの総数 >= corePoolSize の場合、新しいスレッド タスクはタスク キューに入って待機し、その後、アイドル状態のコア スレッドが実行のためにキャッシュ キューからタスクを順番にフェッチします (スレッドの再利用を反映)。
- キャッシュ キューがいっぱいになると、その時点でタスクが多すぎることを意味し、これらのタスクを実行するには「一時的なワーカー」が必要になります。したがって、このタスクを実行するために非コア スレッドが作成されます。この手順にはグローバル ロックが必要であることに注意してください。
- キャッシュ キューがいっぱいで、スレッドの総数が MaximumPoolSize に達すると、上記の拒否戦略が処理に採用されます。
ブロックキュー
プロデューサがリソースを生成し続け、コンシューマがリソースを消費し続けるシナリオを想定します。リソースはバッファ プールに格納されます。プロデューサは、生成されたリソースをバッファ プールに格納します。コンシューマは、消費のためにバッファ プールからリソースを取得します。これは、有名な生産者兼消費者モデル。
このパターンは開発プロセスを簡素化できます。一方で、プロデューサ クラスとコンシューマ クラスの間のコードの依存関係が排除され、他方で、データを生成するプロセスとデータを使用するプロセスが切り離されて、負荷が簡素化されます。
このパターンを自分で実装するコードを作成する場合、複数のスレッドが共有変数 (つまり、リソース) を操作する必要があるため、特に複数のプロデューサーとコンシューマーが存在する場合、スレッドの安全性の問題が発生しやすく、繰り返しの消費とデッドロックが発生します。さらに、バッファ プールが空の場合は、コンシューマをブロックしてプロデューサーをウェイクアップする必要があり、バッファ プールがいっぱいの場合は、プロデューサーをブロックしてコンシューマをウェイクアップする必要があります。これらの待機/ウェイクアップ ロジックは次のようにする必要があります。自分たちで実装しました。
BlockingQueue は、Java util.concurrent パッケージの重要なデータ構造です。通常のキューとは異なり、BlockingQueue は、スレッドセーフなキュー アクセス メソッドを提供します。concurrent パッケージの多くの高度な同期クラスの実装は、BlockingQueue に基づいています。
BlockingQueue は通常、プロデューサー/コンシューマー モデルで使用され、プロデューサーはキューに要素を追加するスレッド、コンシューマーはキューから要素を取得するスレッドです。BlockingQueue は要素を格納するためのコンテナです。
ブロッキング キューは、要素を挿入、削除、検査するための 4 つの異なるメソッド セットを提供します。
メソッド\処理メソッド | 例外をスローする | 特殊な値を返す | 常にブロックされています | タイムアウトで終了 |
---|---|---|---|---|
挿入メソッド | 追加(e) | オファー(e) | 置く(e) | オファー(e、時間、単位) |
除去方法 | 取り除く() | ポーリング() | 取る() | 投票(時間、単位) |
検査方法 | 要素() | ピーク() | - | - |
ブロッキングキュー: BlockingQueue workQueue
- 実行を待機している Runnable タスク オブジェクトを維持します。
- LinkedBlockingQueue
- チェーンされたブロッキング キュー、基礎となるデータ構造はリンク リスト、デフォルト サイズは Integer.MAX_VALUE、サイズも指定されます
- 配列ブロックキュー
- 配列ブロックキュー。基礎となるデータ構造は配列であり、キューのサイズを指定する必要があります。
- 同期キュー
- 同期キュー、内部容量は 0、各 put 操作は take 操作を待機する必要があり、その逆も同様です。
- 遅延キュー
- 遅延キュー。キュー内の要素は、指定された遅延時間が経過したときにのみキューから取得できます。
- 優先ブロッキングキュー
- 無制限のブロッキング キューの場合、優先度はコンストラクターによって渡される Compator オブジェクトによって決定されます。
キューをブロックする原理
ブロッキングキューは主に、Lock ロックの複数条件 (Condition) ブロッキング制御を使用します。
1 つ目はコンストラクターで、キューのサイズと公平なロックかどうかの初期化に加えて、同じロック (ロック) に対する 2 つのモニター (notEmpty と notFull) も初期化します。これら 2 つのモニターの機能は、現時点では単にマーク グループ化として理解できます。スレッドが put 操作を実行しているときは、モニター notFull をそれに追加してスレッドをプロデューサーとしてマークし、スレッドが take 操作を実行しているときは、モニターを追加します。 notEmpty を監視し、このスレッドをコンシューマとしてマークします。