学習内容:漫画: スレッド プール、スレッドの成長/リサイクル戦略についての話
今日、WeChat でプッシュしたときにこのような記事を見て、以前学んだスレッド プールの知識を復習しました。
スレッドプール
システムによるスレッドの頻繁な作成と破棄を避けるために、作成されたスレッドを再利用できます。スレッドの作成はスレッド プールからアイドル状態のスレッドを取得することになり、スレッドを閉じることはスレッドをプールに戻すことになります。
スレッド プールの利点は、
まず、リソース消費の削減です。作成されたスレッドを再利用することで、スレッドの作成と破棄のコストを削減します。
2つ目:応答速度の向上。タスクが完了すると、スレッドの作成を待たずにすぐにタスクを実行できます。
3 番目: スレッドの管理性を向上させます。スレッドは希少なリソースです。無制限に作成すると、システム リソースを消費するだけでなく、システムの安定性も低下します。スレッド プールを使用すると、一元的な割り当て、チューニング、監視が可能になります。
JDK
スレッド プールのサポートはJava1.5
で提供されておりExecutor
、これによりスレッドを効果的に管理および制御できます。その本質もスレッド プールです。
Java のスレッド プールのトップレベル インターフェイスは Executor ですが、実際のスレッド プール インターフェイスは のデフォルト実装でありExecutorService
、共通クラス Executors で呼び出されます。ExecutorService
ThreadPoolExecutor
ThreadPoolExecutor
スレッドプール戦略
スレッドプール内のパラメータの意味
ここでスレッドプールについて再理解しましょう!
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// ...
}
corePoolSize
:コアスレッド数を示します
maximumPoolSize
:最大スレッド数を示し
keepAliveTime
ます :コアスレッド数以外のスレッドの最大アイドル生存時間を示します :時間単位を
unit
示します:スレッドプールのタスク待ちキューを示します:スレッド ファクトリ、スレッド プールに使用されます。 スレッドを作成します。スレッドの名前を設定するために使用できます。通常、デフォルトではパラメータは設定されません。keepAliveTime
workQueue
threadFactory
handler
: 飽和戦略、スレッド プールがタスクを処理できない場合の拒否方法。これは、タスク キューとスレッド プールの両方が満杯の場合に採用される対処戦略であり、デフォルトでは、新しいタスクを処理できないことを示しますAbordPolicy
。そして例外がスローされます ( RejectedExecutionException
)。
CallerRunsPolicy
: 呼び出し元のスレッドを使用してタスクを処理します。この戦略は、新しいタスクの送信を遅くする単純なフィードバック制御メカニズムを提供します。DiscardPoliicy
:タスクは実行できないため、タスクは削除されます。DiscardOldestPolicy
: キュー内の最新のタスクを破棄し、現在のタスクを実行します。
ここでのパラメータは相互に影響しており、たとえば、workQueue
設定が間違っていると役に立たず、maximumPoolSize
スレッド プール内のスレッドが、maximumPoolSize
コア スレッドの数で設定されたスレッド数まで増加することはありません。
スレッドプールでのスレッド成長戦略
スレッド プールの成長戦略は主にこれら 3 つのパラメータに関連します。
corePoolSize
:コアスレッド数
maximumPoolSize
:最大スレッド数
workQueue
:スレッドプールのタスク待ちキュー
スレッド プールのタスク処理戦略:
現在のスレッド プール内のスレッドの数が 未満の場合corePoolSize
、タスクが到着するたびに、タスクを実行するためのスレッドが作成されます。
現在のスレッド プールのスレッド数が >= の場合corePoolSize
、タスクが到着するたびにタスク キャッシュ キューに追加しようとします。追加が成功すると、タスクはアイドル状態のスレッドがそれを取り出すのを待ちます。実行のために; 追加が失敗した場合 (通常、タスク キャッシュ キューがいっぱいの場合)、このタスクを実行するための新しいスレッドの作成が試行されます; 現在のスレッド プール内のスレッド数が に達すると、タスク拒否戦略が採用されmaximumPoolSize
ます処理用。
スレッド プール内のスレッド数が を超えた場合corePoolSize
、スレッド アイドル時間が を超えた場合keepAliveTime
、スレッド プール内のスレッド数が を超えなくなるまで、スレッドは終了しますcorePoolSize
。コア プール内のスレッド、次にコア プール内のスレッド アイドル時間を超えるとkeepAliveTime
、スレッドも終了します。
ここでは、フローチャートを参照して上記のテキストを要約することができます。上の図と組み合わせると、タスクの送信を開始したときにさまざまな状況に遭遇したこと
がわかります( )ThreadPoolExecutor
execute
スレッド数がコアスレッド数(
corePoolSize
)に満たない場合は、タスクを処理するためのコアスレッドを多数作成し、
スレッド数がコア スレッド数 ( ) 以上の場合
corePoolSize
、タスクはタスク キュー ( ) に追加されworkQueue
、スレッド プール内のアイドル スレッドは継続的にタスク キューからタスクを取り出して処理します。 。
タスク キューがいっぱいで、スレッド数が最大スレッド数 ( )に達していない場合
maximumPoolSize
、タスクを処理するために非コア スレッドが作成されます。
スレッド数が最大スレッド数を超える場合、飽和戦略が実行されます。
試験結果
そんなスレッドプールの作成コードを見てみましょう!
public static ExecutorService newThreadPool() {
return new ThreadPoolExecutor(
30, 60,
60L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
60L, TimeUnit.MILLISECONDS,
コアスレッド数が30 、コアスレッド数以外のスレッドを指す最大スレッド数が60、アイドル生存時間の最大値が60ミリ秒、タスク待ちキューが60ミリ秒となっていることがわかります。スレッド プールは ですLinkedBlockingQueue<Runnable>
。
スレッドプールのタスク待ちキューは となっており、ソースコードLinkedBlockingQueue<Runnable>
を見てみましょう。LinkedBlockingQueue<Runnable>
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);
}
LinkedBlockingQueue
このことから、パラメーターを渡さない場合、デフォルトで無制限のキューが作成されることがわかります。
この例から、スレッド プールのタスク待機キューは無制限のキューです。つまり、システム メモリが使い果たされない限り、キューがいっぱいになることはありません。
スレッドプール内のスレッドの縮小戦略
スレッド プールで実行されるタスクは常に実行中に終了します。次に、スレッド プール内にアイドル状態のスレッドが多数ある場合、スレッド プールには、スレッド プール内の冗長スレッドを再利用するための特定の縮小戦略も適用されます。
スレッド プール内のスレッドの縮小戦略は、次のパラメータに関連しています。
corePoolSize
: コア スレッドの数; :
maximumPoolSize
スレッド プール内のスレッドの最大数; :コア スレッドの数以外のスレッドのアイドル状態の存続期間
keepAliveTime
;期間の単位。
unit
keepAliveTime
corePoolSize
maximumPoolSize
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にすることはできないということです。
隙間をチェックする
待機キューは拒否ポリシーにも影響します
待機キューが無制限キューとして構成されている場合、コア スレッド数から最大スレッド数までのスレッド数の増加に影響するだけでなく、構成された拒否ポリシーが実行されなくなります。
スレッド プール内のワーカー スレッドの数がコア スレッドの数に達し、その時点で待機キューがいっぱいの場合にのみ、拒否ポリシーが有効になるためです。
コアスレッドの数を「ウォームアップ」できる
前述したように、デフォルトでは、スレッド プール内のスレッドはタスクに応じて増加します。ただし、必要に応じて、スレッド プールのコア スレッドを事前に準備して、突然の同時実行性の高いタスクに対処することもできます (たとえば、スナップアップ システムではそのようなニーズがよくあります)。
prestartCoreThread()
このとき、やを使ってprestartAllCoreThreads()
あらかじめ芯糸を作成しておく方法を「プレヒート」といいます。
無制限のキューが必要なシナリオの場合、どうすればよいですか?
要件は変更可能であり、無制限のキューを使用する必要があるシナリオに必ず遭遇するため、このシナリオの構成はmaximumPoolSize
無効です。
この時点で、 Executors でスレッド プールを作成するプロセスを参照しnewFixedThreadPool()
、一貫性corePoolSize
を保つことができますmaximumPoolSize
。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
このときのコアスレッド数は最大スレッド数となり、この数まで増加した場合のみタスクは待機キューに入れられ、設定したスレッド数が確実に使用されるようになります。