newCachedThreadPool()
対newFixedThreadPool()
いつどちらを使うべきですか?リソース使用率の観点から、どちらの戦略が優れていますか?
newCachedThreadPool()
対newFixedThreadPool()
いつどちらを使うべきですか?リソース使用率の観点から、どちらの戦略が優れていますか?
ドキュメントは、これら2つの関数の違いと使用法をかなりよく説明していると思います。
共有の無制限キューで動作する固定数のスレッドを再利用するスレッドプールを作成します。どの時点でも、最大でnThreadsスレッドがアクティブな処理タスクになります。すべてのスレッドがアクティブなときに追加のタスクが送信されると、スレッドが使用可能になるまでキューで待機します。シャットダウン前の実行中に障害が発生したためにスレッドが終了した場合、後続のタスクを実行するために必要な場合は、新しいスレッドが代わりに使用されます。プール内のスレッドは、明示的にシャットダウンされるまで存在します。
必要に応じて新しいスレッドを作成するスレッドプールを作成しますが、以前に作成されたスレッドが利用可能になったときに再利用します。これらのプールは通常、多くの短期間の非同期タスクを実行するプログラムのパフォーマンスを向上させます。実行するための呼び出しは、利用可能な場合、以前に構築されたスレッドを再利用します。使用可能な既存のスレッドがない場合は、新しいスレッドが作成され、プールに追加されます。60秒間使用されなかったスレッドは終了し、キャッシュから削除されます。したがって、十分長い間アイドル状態のままであるプールは、リソースを消費しません。ThreadPoolExecutorコンストラクターを使用して、プロパティは似ているが詳細(タイムアウトパラメーターなど)が異なるプールを作成できることに注意してください。
リソースに関しては、newFixedThreadPool
は明示的に終了するまですべてのスレッドを実行し続けます。newCachedThreadPool
60秒間使用されなかったスレッドでは、終了してキャッシュから削除されます。
これを考えると、リソース消費は状況に大きく依存します。たとえば、長時間実行されるタスクが大量にある場合は、をお勧めしFixedThreadPool
ます。についてはCachedThreadPool
、ドキュメントには「これらのプールは通常、多くの短期間の非同期タスクを実行するプログラムのパフォーマンスを向上させる」と書かれています。
他の答えを完成させるために、JoshuaBlochによるEffectiveJava、2nd Edition、第10章、アイテム68を引用したいと思います。
「特定のアプリケーションのエグゼキュータサービスを選択するのは難しい場合があります。小さなプログラムや負荷の少ないサーバーを作成している場合は、Executors.new- CachedThreadPoolを使用することをお勧めします。これは、構成を必要とせず、通常は「正しいことです。」ただし、キャッシュされたスレッドプールは、負荷の高い本番サーバーには適していません。
キャッシュされたスレッドプールでは、送信されたタスクはキューに入れられませんが、実行のためにすぐにスレッドに渡されます。使用可能なスレッドがない場合は、新しいスレッドが作成されます。サーバーの負荷が非常に高く、すべてのCPUが完全に使用され、より多くのタスクが到着すると、より多くのスレッドが作成され、事態はさらに悪化します。
したがって、負荷の高い本番サーバーでは、 Executors.newFixedThreadPoolを使用することをお勧めします。これにより、スレッド数が固定されたプールが提供されるか、ThreadPoolExecutorクラスを直接使用して最大限の制御が可能になります。「」
ソースコードを見ると、 ThreadPoolExecutorを呼び出していることがわかります。内部的におよびそれらのプロパティを設定します。要件をより適切に制御するために作成できます。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
呼び出し可能/実行可能タスクの無制限のキューについて心配していない場合は、そのうちの1つを使用できます。ブルーノが示唆しているように、私もこれら2つよりも好きnewFixedThreadPool
です。newCachedThreadPool
ただし、ThreadPoolExecutor は、newFixedThreadPool
またはnewCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
利点:
BlockingQueueのサイズを完全に制御できます。前の2つのオプションとは異なり、無制限ではありません。システムに予期しない混乱が発生した場合に、保留中のCallable / Runnableタスクが大量に積み重なるため、メモリ不足エラーが発生することはありません。
カスタム拒否処理ポリシーを実装するか、次のいずれかのポリシーを使用できます。
デフォルトThreadPoolExecutor.AbortPolicy
では、ハンドラーは拒否時にランタイムRejectedExecutionExceptionをスローします。
ではThreadPoolExecutor.CallerRunsPolicy
、execute自体を呼び出すスレッドがタスクを実行します。これにより、新しいタスクが送信される速度を遅くする単純なフィードバック制御メカニズムが提供されます。
ではThreadPoolExecutor.DiscardPolicy
、実行できないタスクは単にドロップされます。
ではThreadPoolExecutor.DiscardOldestPolicy
、エグゼキュータがシャットダウンされていない場合、ワークキューの先頭にあるタスクがドロップされ、実行が再試行されます(これにより、再度失敗し、これが繰り返される可能性があります)。
以下のユースケース用にカスタムスレッドファクトリを実装できます。
このThreadPoolExecutor
クラスは、多くのExecutors
ファクトリメソッドから返されるエグゼキュータの基本実装です。それでは、の観点から固定スレッドプールとキャッシュスレッドプールにアプローチしましょう。ThreadPoolExecutor
このクラスのメインコンストラクターは次のようになります。
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
corePoolSize
は、ターゲットスレッドプールの最小サイズを決定します。実行するタスクがない場合でも、実装はそのサイズのプールを維持します。
はmaximumPoolSize
、一度にアクティブにできるスレッドの最大数です。
スレッドプールが大きくなり、corePoolSize
しきい値より大きくなった後、エグゼキュータはアイドル状態のスレッドを終了して、corePoolSize
再び到達することができます。trueの場合allowCoreThreadTimeOut
、エグゼキュータは、コアプールスレッドがkeepAliveTime
しきい値を超えてアイドル状態だった場合でも、それらを終了できます。
つまり、スレッドがkeepAliveTime
しきい値を超えてアイドル状態のままである場合、スレッドに対する需要がないため、スレッドが終了する可能性があります。
新しいタスクが入り、すべてのコアスレッドが占有されている場合はどうなりますか?新しいタスクは、そのBlockingQueue<Runnable>
インスタンス内でキューに入れられます。スレッドが解放されると、それらのキューに入れられたタスクの1つを処理できます。
BlockingQueue
Javaにはさまざまなインターフェースの実装があるため、次のようなさまざまなキューイングアプローチを実装できます。
制限付きキュー:新しいタスクは、制限付きタスクキュー内にキューに入れられます。
無制限のキュー:新しいタスクは、無制限のタスクキュー内にキューに入れられます。したがって、このキューはヒープサイズが許す限り大きくなる可能性があります。
同期ハンドオフ:を使用しSynchronousQueue
て新しいタスクをキューに入れることもできます。その場合、新しいタスクをキューに入れるときに、別のスレッドがそのタスクをすでに待機している必要があります。
ThreadPoolExecutor
が新しいタスクを実行する方法は次のとおりです。
corePoolSize
は、指定されたタスクを最初のジョブとして新しいスレッドを開始しようとします。BlockingQueue#offer
それ以外の場合は、メソッドを使用して新しいタスクをキューに入れようとします
。キューがいっぱいのoffer
場合、メソッドはブロックせず、すぐにを返しますfalse
。offer
返すfalse
場合)、このタスクを最初のジョブとして、スレッドプールに新しいスレッドを追加しようとします。RejectedExecutionHandler
ます。固定スレッドプールとキャッシュスレッドプールの主な違いは、次の3つの要素に要約されます。
+ ----------- + ----------- + ------------------- + ----- ---------------------------- + | プールタイプ| コアサイズ| 最大サイズ| キューイング戦略| + ----------- + ----------- + ------------------- + ----- ---------------------------- + | 修正済み| n(固定)| n(固定)| 無制限の`LinkedBlockingQueue`| + ----------- + ----------- + ------------------- + ----- ---------------------------- + | キャッシュ| 0 | Integer.MAX_VALUE | `SynchronousQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- +
Excutors.newFixedThreadPool(n)
とおりです。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ご覧のように:
OutOfMemoryError
。いつどちらを使うべきですか?リソース使用率の観点から、どちらの戦略が優れていますか?
リソース管理の目的で同時タスクの数を制限する場合は、固定サイズのスレッドプールが適しているようです。
たとえば、エグゼキュータを使用してWebサーバー要求を処理する場合、固定エグゼキュータは要求バーストをより合理的に処理できます。
さらに優れたリソース管理のために、合理的なと組み合わせThreadPoolExecutor
た制限付きのBlockingQueue<T>
実装でカスタムを作成することを強くお勧めしますRejectedExecutionHandler
。
仕組みは次のExecutors.newCachedThreadPool()
とおりです。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ご覧のように:
Integer.MAX_VALUE
。実際には、スレッドプールには制限がありません。SynchronousQueue
を受け入れる相手がいない場合、常に失敗するためです。いつどちらを使うべきですか?リソース使用率の観点から、どちらの戦略が優れていますか?
予測可能な短期間のタスクがたくさんある場合に使用します。
そうExecutors.newCachedThreadPool()
です、複数のクライアントと同時リクエストを処理するサーバーコードには適していません。
なんで?これには基本的に2つの(関連する)問題があります。
これは無制限です。つまり、サービスにさらに多くの作業を注入するだけで、誰もがJVMを機能不全に陥れる可能性があります(DoS攻撃)。スレッドは無視できない量のメモリを消費し、進行中の作業に基づいてメモリ消費量も増加するため、この方法でサーバーを倒すのは非常に簡単です(他のサーキットブレーカーが設置されていない場合)。
無制限の問題は、エグゼキュータの前にSynchronousQueue
タスクギバーとスレッドプールの間に直接のハンドオフがあることを意味するという事実によって悪化します。既存のすべてのスレッドがビジーの場合、新しいタスクごとに新しいスレッドが作成されます。これは通常、サーバーコードにとって悪い戦略です。CPUが飽和状態になると、既存のタスクの完了に時間がかかります。さらに多くのタスクが送信され、より多くのスレッドが作成されるため、タスクの完了にはますます時間がかかります。CPUが飽和状態になると、サーバーが必要とするスレッドが増えることは間違いありません。
これが私の推奨事項です:
固定サイズのスレッドプールExecutors.newFixedThreadPoolまたは ThreadPoolExecutorを使用します。スレッドの最大数が設定されています。
newCachedThreadPoolを使用する必要があるのは、Javadocに記載されているように短期間の非同期タスクがある場合のみです。処理に時間がかかるタスクを送信すると、作成されるスレッドが多すぎます。長時間実行されるタスクをより高速でnewCachedThreadPool(http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/)に送信すると、CPUが100%に達する可能性があります。
私はいくつかの簡単なテストを行い、次の結果があります。
1)SynchronousQueueを使用する場合:
スレッドが最大サイズに達すると、以下のような例外を除いて、新しい作業は拒否されます。
スレッド「メイン」の例外java.util.concurrent.RejectedExecutionException:タスクjava.util.concurrent.FutureTask@3fee733dがjava.util.concurrent.ThreadPoolExecutor@5acf9800から拒否されました[実行中、プールサイズ= 3、アクティブスレッド= 3、キューに入れられたタスク= 0、完了したタスク= 0]
java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)で
2)LinkedBlockingQueueを使用する場合:
スレッドが最小サイズから最大サイズに増加することはありません。つまり、スレッドプールは最小サイズとして固定サイズになります。