一、序
public static ExecutorService newThreadPool() {
return new ThreadPoolExecutor(
30, 60,
60L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
今日、この質問を借りて、スレッドプールで維持されているスレッドについて話しましょう。その成長とリサイクルの戦略は何ですか?
第二に、スレッドプール戦略
2.1スレッドプールパラメーター
スレッドプールでのスレッド拡張戦略について話すとき、最も目を引くのはそのコアスレッド番号(corePoolSize)と最大スレッド番号(maximumPoolSize)ですが、これら2つのパラメーターを見ると、十分ではないため、スレッドの数は増加は、タスク待機キューにも関連しています。
ThreadPoolExecutorの最も完全なパラメーター構築メソッドを見てみましょう。
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// ...
}
各パラメータの意味を簡単に説明します。
- corePoolSize:コアスレッドの数。
- maximumPoolSize:スレッドプール内のスレッドの最大数。
- keepAliveTime:コアスレッドの数、最大アイドル存続時間以外のスレッド。
- unit:keepAliveTimeの時間単位。
- workQueue:スレッドプールのタスク待機キュー。
- threadFractory:スレッドファクトリ。スレッドプールのスレッドを作成するために使用されます。
- ハンドラー:スレッドプールがタスクを処理できない場合の拒否戦略。
これらのパラメータの多くの設定は互いに影響します。たとえば、タスク待機キューworkQueueの構成が不適切な場合、スレッドプール内のスレッドが、構成されているコアスレッドの数(maximumPoolSize)まで成長しないことがあります。
2.2スレッドプール内のスレッドの成長戦略
スレッドプールスレッドの成長戦略は、次の3つのパラメーターに関連しています。
- corePoolSize:コアスレッドの数
- maximumPoolSize:スレッドの最大数。
- workQueue:タスクキューを待っています。
彼らの以前の関係はこれです:
次に、スレッドプール内のスレッドの成長の理想的な戦略について説明します。
デフォルトでは、スレッドプールは最初は空ですが、新しいタスクが来ると、スレッドプールはスレッドファクトリ(threadFractory)を介してスレッドを作成し、タスクを処理します。
新しいタスクは、スレッドの数がコアスレッド番号(corePoolSize)に達するまで、スレッドプール内のスレッドの作成をトリガーし続けます。次に、スレッドの作成が停止され、新しいタスクがタスク待機キュー(workQueue)に入れられます。
新しいタスクは引き続きタスク待機キューに入り、キューがいっぱいになると、スレッドプール内のスレッド数がmaximumPoolSize構成に達するまで、スレッド処理タスクの再作成が開始されます。
このステップでは、スレッドプール内のスレッド数が最大に達し、アイドルスレッドはなくなり、タスクキューはタスクでいっぱいになります。このとき、新しいタスクが入ってくると、スレッドプールの拒否戦略(ハンドラー)がトリガーされます。拒否ポリシーが構成されていない場合、RejectedExecutionExceptionがスローされます。
この時点で、スレッドの成長戦略は明確です。次の図でプロセス全体を理解できます。
最も重要なものの1つはタスクの待機キューです。待機キューの実装構造がどのようなものであっても、それがいっぱいになった場合にのみ、スレッドプール内のスレッドは最大スレッド数まで増加します。ただし、キューがいっぱいになるためには、境界キューである必要があります。
これは、記事の最初の例の隠れた穴です。先に作成したスレッドプールを確認してみましょう。
public static ExecutorService newThreadPool() {
return new ThreadPoolExecutor(
30, 60,
60L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
スレッドの最大数はコアスレッドの数より多いが、待機キューはLinkedBlockingQueueで構成されていることがわかります。名前から、これはリンクリストに基づくブロッキングキューであり、デフォルトのコンストラクターであることがわかります構築されると、その容量はInteger.MAX_VALUE
無制限のキューであることを理解するために単純に設定され ます。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
そのため、この方法で構築されたスレッドプールとコアスレッド数の構成パラメーターは、待機キューがいっぱいになることがないため、使用できません。
2.3スレッドプールでのスレッド縮小戦略
スレッドプールで実行されるタスクには、常に実行の終了があります。次に、スレッドプールに多数のアイドルスレッドがある場合、スレッドプール内の冗長スレッドを再利用するための特定の縮小戦略もあります。
スレッドプール内のスレッドの縮小戦略は、次のパラメーターに関連しています。
- corePoolSize:コアスレッドの数。
- maximumPoolSize:スレッドプール内のスレッドの最大数。
- keepAliveTime:コアスレッドの数、アイドル状態が存続する時間の長さ以外のスレッド。
- unit:keepAliveTimeの時間単位。
corePoolSizeとmaximumPoolSizeに精通しており、それを制御できるもう1つの要素は、keepAliveTimeアイドル生存時間とこの時間の単位です。
スレッドプール内のスレッド数がコアスレッド数を超えた場合。このとき、タスク数が少なくなるとタスクが実行されないアイドル状態のスレッドが確実に存在します。次に、このスレッドのアイドル時間がkeepAliveTime&unitの構成された期間を超えると、リサイクルされます。
スレッドプールの場合、それはスレッドの管理のみを担当し、作成されたスレッドはいわゆる「コアスレッド」と「非コアスレッド」を区別せず、スレッドプール内のスレッドの総数のみを管理することに注意してください。リサイクルされたスレッドの数がcorePoolSizeに達すると、リサイクルプロセスが停止します。
スレッドプール内のコアスレッド数のスレッドには、リサイクルの方法もあります。この方法でallowCoreThreadTimeOut(true)
設定でき ます。コアスレッドがアイドル状態の場合、keepAliveTime&unitで構成された時間を超えると、リサイクルされます。
public void allowCoreThreadTimeOut(boolean value) {
if (value && keepAliveTime <= 0)
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
if (value)
interruptIdleWorkers();
}
}
allowCoreThreadTimeOut()
設定できる前提は、keepAliveTimeを0にできないことです。
2.3ギャップのチェック
1. 待機キューも拒否戦略に影響します
待機キューが無制限キューとして構成されている場合、コアスレッド数から最大スレッド数までのスレッド数の増加に影響するだけでなく、構成された拒否戦略が実行されないこともあります。
スレッドプール内のワーカースレッドの数がコアスレッドの数に達し、このときに待機キューもいっぱいになった場合にのみ、拒否戦略が有効になるためです。
2. コアスレッドの数を「ウォームアップ」できる
前述のように、デフォルトでは、スレッドプール内のスレッドはタスクに従って増加します。ただし、必要に応じて、スレッドプールのコアスレッドを事前に準備して、突然の同時実行性の高いタスクに対処することもできます。
この時点では使用することができる prestartCoreThread()
か prestartAllCoreThreads()
、事前に私たちが呼ぶこのようコアスレッドを作成するには、「暖かい」を
3. 無制限のキューが必要なシナリオではどうすればよいですか?
要求は変更可能です。無制限のキューが必要なシナリオに確実に遭遇し、このシナリオで構成されたmaximumPoolSizeが無効になります。
この時点で、ExecutorsでnewFixedThreadPool()
スレッドプールを作成するプロセスを参照して 、corePoolSizeとmaximumPoolSizeの一貫性を保つことができます。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
現時点では、コアスレッドの数は最大スレッド数です。この数が増加した場合にのみ、タスクが待機キューに配置され、構成したスレッド数が使用されるようになります。
4. スレッドプールは公平ですか。
いわゆる公平性とは、先着順のタスクが最初に実行されることを意味します。これは明らかにスレッドプールでは不公平です。
言うまでもなく、スレッドプール内のスレッド実行タスクはシステムを介してスケジュールされるため、タスクの実行順序を保証できないと判断されますが、これは不公平です。また、スレッドプール自体の観点からのみ、送信されたタスクの順序のみを見ており、不公平でもあります。
まず、スレッドプールのコアスレッドが割り当てられている場合、タスクはタスクキューに入り、タスクキューがいっぱいの場合、新しいタスクはスレッドプールに新しく作成されたスレッドによって直接処理されます。スレッド数が最大スレッド数に達するまで。
このとき、タスクキュー内のタスクは、最初にスレッドプールに追加されて処理されますが、これらのタスクの処理タイミングは、新しく作成されたスレッドのタスクよりも遅いため、タスクのみの観点からは不公平ですの。
3.まとめの瞬間
この記事では、スレッドプール内のスレッド数を増減する方法について説明しました。
ここで簡単に要約します:
1. 成長戦略。デフォルトでは、スレッドプールはタスクに基づいて、タスクを実行するのに十分な数のコアスレッドを作成します。コアスレッドがいっぱいになると、タスクは待機キューに配置されます。キューがいっぱいになったら、停止するスレッドの最大数に達するまで、新しいスレッドを作成してタスクを実行し続けます。新しいタスクがある場合、それは拒否戦略を実行するか、例外をスローすることができるだけです。
2. 収縮戦略。スレッドプールスレッドの数がコアスレッドの数より多く、&&現在アイドルスレッドがあり、&&アイドルスレッドのアイドル時間がkeepAliveTimeより大きい場合、スレッドの数がコアスレッドの数と等しくなるまでアイドルスレッドがリサイクルされます。
要するに、制限のないキューを注意して使用することを忘れないでください。