newCachedThreadPool()
対 newFixedThreadPool()
どちらを使用すればよいですか?リソース使用率の観点から、どの戦略が優れていますか?
newCachedThreadPool()
対 newFixedThreadPool()
どちらを使用すればよいですか?リソース使用率の観点から、どの戦略が優れていますか?
回答:
私はドキュメントがこれら2つの関数の違いと使い方をかなりよく説明していると思います:
共有の無制限のキューで動作する一定数のスレッドを再利用するスレッドプールを作成します。どの時点でも、最大でnThreadsスレッドがアクティブな処理タスクになります。すべてのスレッドがアクティブなときに追加のタスクが送信されると、それらはスレッドが使用可能になるまでキューで待機します。シャットダウン前の実行中にエラーが発生してスレッドが終了した場合、後続のタスクを実行する必要がある場合は、新しいスレッドが代わりに実行されます。プール内のスレッドは、明示的にシャットダウンされるまで存在します。
必要に応じて新しいスレッドを作成するスレッドプールを作成しますが、以前に構築されたスレッドが利用可能な場合は再利用します。これらのプールは通常、短期間の非同期タスクを多数実行するプログラムのパフォーマンスを向上させます。executeを呼び出すと、以前に構築されたスレッドが利用可能な場合は再利用されます。利用可能な既存のスレッドがない場合、新しいスレッドが作成され、プールに追加されます。60秒間使用されなかったスレッドは終了し、キャッシュから削除されます。したがって、十分に長い間アイドル状態のままであるプールは、リソースを消費しません。プロパティは似ているが詳細(タイムアウトパラメータなど)が異なるプールは、ThreadPoolExecutorコンストラクタを使用して作成できることに注意してください。
リソースに関しては、newFixedThreadPool
明示的に終了されるまで、はすべてのスレッドを実行し続けます。newCachedThreadPool
60秒間使用されなかったスレッドは終了し、キャッシュから削除されます。
これを考えると、リソースの消費は状況に大きく依存します。たとえば、長時間実行するタスクが大量にある場合は、をお勧めしFixedThreadPool
ます。についてはCachedThreadPool
、ドキュメントは「これらのプールは通常、多くの短期間の非同期タスクを実行するプログラムのパフォーマンスを向上させる」と述べています。
他の回答を完了するために、Joshua Blochによる第10章の項目68によるEffective Java、第2版を引用したいと思います。
「特定のアプリケーションのためのエグゼキュータのサービスを選択すると、トリッキーなことができます。あなたが書いている場合は、小さなプログラムを、または負荷の軽いサーバー使用して、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>());
}
Callable / Runnableタスクの無制限のキューについて心配していない場合は、それらのいずれかを使用できます。ブルーノによって示唆されるように、私も好みnewFixedThreadPool
にnewCachedThreadPool
これら2オーバー。
ただし、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
、エグゼキュータがシャットダウンされていない場合、ワークキューの先頭にあるタスクが削除されてから、実行が再試行されます(これは再び失敗し、これが繰り返される可能性があります)。
以下のユースケースのカスタムスレッドファクトリを実装できます。
そうです、これExecutors.newCachedThreadPool()
は、複数のクライアントと同時要求にサービスを提供するサーバーコードにはあまり適していません。
どうして?基本的には2つの(関連する)問題があります。
それは無制限です。つまり、サービスにより多くの作業を注入するだけで、誰でもあなたのJVMに障害を与えることができます(DoS攻撃)。スレッドは無視できない量のメモリを消費し、進行中の作業に基づいてメモリ消費も増加するため、この方法でサーバーを転倒させることは非常に簡単です(他のサーキットブレーカーがない場合を除く)。
無限の問題は、エグゼキュータの前にaがSynchronousQueue
置かれているという事実によって悪化します。これは、タスクギバーとスレッドプールの間に直接ハンドオフがあることを意味します。既存のすべてのスレッドがビジーの場合、新しいタスクごとに新しいスレッドが作成されます。これは通常、サーバーコードの悪い戦略です。CPUが飽和すると、既存のタスクの完了に時間がかかります。それでも、より多くのタスクが送信され、より多くのスレッドが作成されるため、タスクの完了に時間がかかります。CPUが飽和状態になると、より多くのスレッドがサーバーに必要なものではなくなります。
これが私の推奨事項です:
固定サイズのスレッドプールExecutors.newFixedThreadPoolまたは ThreadPoolExecutorを使用します。スレッドの最大数が設定されています。
ThreadPoolExecutor
クラスは、多くのから返されるのエグゼキュータのための基本実装ですExecutors
ファクトリメソッド。それでは、FixedおよびCachedスレッドプールにアプローチしてみましょう。ThreadPoolExecutor
観点。
の このクラスメインコンストラクタは次のようになります。
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
corePoolSize
ターゲットスレッドプールの最小サイズを決定します。実装は、実行するタスクがない場合でも、そのサイズのプールを維持します。
の maximumPoolSize
一度にアクティブにできるスレッドの最大数です。
スレッドプールが大きくなり、corePoolSize
しきい値よりも大きくなると、エグゼキュータはアイドルスレッドを終了して、corePoolSize
再びスレッドに到達できます。もしallowCoreThreadTimeOut
trueの、executorは、コアプールスレッドが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
ことは、反対側に受け入れるタスクがない場合に常に失敗するからです。どちらを使用すればよいですか?リソース使用率の観点から、どの戦略が優れていますか?
予測可能な短期実行タスクが多数ある場合に使用します。
JavaCacheに記載されているように、存続期間の短い非同期タスクがある場合にのみnewCachedThreadPoolを使用する必要があります。処理に時間がかかるタスクを送信すると、スレッドが大量に作成されることになります。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を使用する場合:
スレッドが最小サイズから最大サイズに増加することはありません。つまり、スレッドプールは最小サイズとして固定サイズです。