Executors.newCachedThreadPool()とExecutors.newFixedThreadPool()の比較


回答:


202

私はドキュメントがこれら2つの関数の違いと使い方をかなりよく説明していると思います:

newFixedThreadPool

共有の無制限のキューで動作する一定数のスレッドを再利用するスレッドプールを作成します。どの時点でも、最大でnThreadsスレッドがアクティブな処理タスクになります。すべてのスレッドがアクティブなときに追加のタスクが送信されると、それらはスレッドが使用可能になるまでキューで待機します。シャットダウン前の実行中にエラーが発生してスレッドが終了した場合、後続のタスクを実行する必要がある場合は、新しいスレッドが代わりに実行されます。プール内のスレッドは、明示的にシャットダウンされるまで存在します。

newCachedThreadPool

必要に応じて新しいスレッドを作成するスレッドプールを作成しますが、以前に構築されたスレッドが利用可能な場合は再利用します。これらのプールは通常、短期間の非同期タスクを多数実行するプログラムのパフォーマンスを向上させます。executeを呼び出すと、以前に構築されたスレッドが利用可能な場合は再利用されます。利用可能な既存のスレッドがない場合、新しいスレッドが作成され、プールに追加されます。60秒間使用されなかったスレッドは終了し、キャッシュから削除されます。したがって、十分に長い間アイドル状態のままであるプールは、リソースを消費しません。プロパティは似ているが詳細(タイムアウトパラメータなど)が異なるプールは、ThreadPoolExecutorコンストラクタを使用して作成できることに注意してください。

リソースに関しては、newFixedThreadPool明示的に終了されるまで、はすべてのスレッドを実行し続けます。newCachedThreadPool60秒間使用されなかったスレッドは終了し、キャッシュから削除されます。

これを考えると、リソースの消費は状況に大きく依存します。たとえば、長時間実行するタスクが大量にある場合は、をお勧めしFixedThreadPoolます。についてはCachedThreadPool、ドキュメントは「これらのプールは通常、多くの短期間の非同期タスクを実行するプログラムのパフォーマンスを向上させる」と述べています。


1
はい、私はdocsを通過しました。問題は... fixedThreadPoolがメモリ不足エラーを@ 3スレッドで引き起こしています... cachedPoolが内部的に単一のスレッドのみを作成しているためです。両方のパフォーマンス..他に欠けているものはありますか?
2009年

1
ThreadPoolにThreadfactoryを提供していますか?私の推測では、ガベージコレクションされていない状態がスレッドに保存されている可能性があります。そうでない場合、プログラムがヒープ制限サイズに非常に近く実行されているため、3つのスレッドを作成するとOutOfMemoryが発生します。また、cachedPoolが内部で単一のスレッドのみを作成している場合、これは、タスクが同期して実行されていることを示しています。
ブルーノコンデ

@Louis F.が指摘するように@brunocondeだけではnewCachedThreadPool、いくつかの可能性があります深刻なあなたにすべてのコントロールを残すための問題をthread pool、いつサービスが同じで他の人と協力してホスト長時間CPU待ちのために他の人がクラッシュ引き起こす可能性があります。したがってnewFixedThreadPool、このようなシナリオではより安全になると思います。また、この投稿では、両者の最も顕著な違いを明らかにしています。
2018年

75

他の回答を完了するために、Joshua Blochによる第10章の項目68によるEffective Java、第2版を引用したいと思います。

「特定のアプリケーションのためのエグゼキュータのサービスを選択すると、トリッキーなことができます。あなたが書いている場合は、小さなプログラムを、または負荷の軽いサーバー使用して、Executors.new- CachedThreadPoolは、一般的に良い選択、それは一般的に何も設定を要求していないとして、「ありません正しいことです。」しかし、キャッシュされたスレッドプールは、負荷高い運用サーバーには適していません

では、キャッシュされたスレッドプール提出されたタスクがキューイングされていませんが、すぐに実行するためのスレッドに渡さ。使用可能なスレッドがない場合は、新しいスレッドが作成されます。サーバーの負荷が非常に高く、すべてのCPUが完全に利用されている場合、より多くのタスクが到着すると、より多くのスレッドが作成され、問題はさらに悪化します。

したがって、負荷の高い運用サーバーではExecutors.newFixedThreadPoolを使用して、固定数のスレッドを持つプールを提供するか、またはThreadPoolExecutorクラスを直接使用することで、最大限の制御が得られます。


15

あなたが見れば ソースコードを、次のように表示されます、彼らは呼んでいる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
正確に言うと、キャッシュスレッドエグゼキューターは、まともな上限があり、たとえば5〜10分のアイドルリーピングは、ほとんどの場合に最適です。
Agoston Horvath

12

Callable / Runnableタスクの無制限のキューについて心配していない場合は、それらのいずれかを使用できます。ブルーノによって示唆されるように、私も好みnewFixedThreadPoolnewCachedThreadPoolこれら2オーバー。

ただし、ThreadPoolExecutor は、newFixedThreadPoolまたはに比べてより柔軟な機能を提供します。newCachedThreadPool

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

利点:

  1. BlockingQueueのサイズを完全に制御できます。以前の2つのオプションとは異なり、無制限ではありません。システムに予期しない乱流がある場合、保留中のCallable / Runnableタスクが大量に蓄積されるため、メモリ不足エラーは発生しません。

  2. カスタム拒否処理ポリシーを実装するか、いずれかのポリシーを使用できます。

    1. デフォルトThreadPoolExecutor.AbortPolicyでは、ハンドラーは拒否時にランタイムRejectedExecutionExceptionをスローします。

    2. ではThreadPoolExecutor.CallerRunsPolicy、execute自体を呼び出すスレッドがタスクを実行します。これにより、新しいタスクが送信される速度を遅くする単純なフィードバック制御メカニズムが提供されます。

    3. ではThreadPoolExecutor.DiscardPolicy、実行できないタスクは単に削除されます。

    4. ではThreadPoolExecutor.DiscardOldestPolicy、エグゼキュータがシャットダウンされていない場合、ワークキューの先頭にあるタスクが削除されてから、実行が再試行されます(これは再び失敗し、これが繰り返される可能性があります)。

  3. 以下のユースケースのカスタムスレッドファクトリを実装できます。

    1. よりわかりやすいスレッド名を設定するには
    2. スレッドデーモンのステータスを設定するには
    3. スレッドの優先順位を設定するには

11

そうです、これExecutors.newCachedThreadPool()は、複数のクライアントと同時要求にサービスを提供するサーバーコードにはあまり適していません。

どうして?基本的には2つの(関連する)問題があります。

  1. それは無制限です。つまり、サービスにより多くの作業を注入するだけで、誰でもあなたのJVMに障害を与えることができます(DoS攻撃)。スレッドは無視できない量のメモリを消費し、進行中の作業に基づいてメモリ消費も増加するため、この方法でサーバーを転倒させることは非常に簡単です(他のサーキットブレーカーがない場合を除く)。

  2. 無限の問題は、エグゼキュータの前にaがSynchronousQueue置かれているという事実によって悪化します。これは、タスクギバーとスレッドプールの間に直接ハンドオフがあることを意味します。既存のすべてのスレッドがビジーの場合、新しいタスクごとに新しいスレッドが作成されます。これは通常、サーバーコードの悪い戦略です。CPUが飽和すると、既存のタスクの完了に時間がかかります。それでも、より多くのタスクが送信され、より多くのスレッドが作成されるため、タスクの完了に時間がかかります。CPUが飽和状態になると、より多くのスレッドがサーバーに必要なものではなくなります。

これが私の推奨事項です:

固定サイズのスレッドプールExecutors.newFixedThreadPoolまたは ThreadPoolExecutorを使用します。スレッドの最大数が設定されています。


6

ThreadPoolExecutorクラスは、多くのから返されるのエグゼキュータのための基本実装ですExecutorsファクトリメソッド。それでは、FixedおよびCachedスレッドプールにアプローチしてみましょう。ThreadPoolExecutor観点。

ThreadPoolExecutor

このクラスメインコンストラクタは次のようになります。

public ThreadPoolExecutor(
                  int corePoolSize,
                  int maximumPoolSize,
                  long keepAliveTime,
                  TimeUnit unit,
                  BlockingQueue<Runnable> workQueue,
                  ThreadFactory threadFactory,
                  RejectedExecutionHandler handler
)

コアプールサイズ

corePoolSizeターゲットスレッドプールの最小サイズを決定します。実装は、実行するタスクがない場合でも、そのサイズのプールを維持します。

最大プールサイズ

maximumPoolSize一度にアクティブにできるスレッドの最大数です。

スレッドプールが大きくなり、corePoolSizeしきい値よりも大きくなると、エグゼキュータはアイドルスレッドを終了して、corePoolSize再びスレッドに到達できます。もしallowCoreThreadTimeOuttrueの、executorは、コアプールスレッドがkeepAliveTimeしきい値を超えてアイドル状態であった場合でも終了できます。

つまり、最終的には、スレッドがアイドル状態のままである場合、 keepAliveTimeしきい値を超え場合、要求がないために終了する可能性があります。

キューイング

新しいタスクが入り、すべてのコアスレッドが占有されるとどうなりますか?新しいタスクはその中にキューに入れられますBlockingQueue<Runnable>インスタンスます。スレッドが解放されると、キューに入れられたタスクの1つを処理できます。

BlockingQueueJava のインターフェースにはさまざまな実装があるため、次のようなさまざまなキューイング手法を実装できます。

  1. 制限付きキュー:新しいタスクは、制限付きタスクキュー内にキューイングされます。

  2. 無制限のキュー:新しいタスクは、無制限のタスクキュー内にキューイングされます。したがって、このキューはヒープサイズが許す限り大きくなる可能性があります。

  3. 同期ハンドオフ:を使用しSynchronousQueueて、新しいタスクをキューに入れることもできます。その場合、新しいタスクをキューに入れるとき、別のスレッドがそのタスクをすでに待っている必要があります。

作品提出

ThreadPoolExecutorが新しいタスクを実行する方法は次のとおりです。

  1. corePoolSize実行されているスレッドが少ない場合、指定されたタスクを最初のジョブとして新しいスレッドを開始しようとします。
  2. それ以外の場合は、BlockingQueue#offerメソッドを使用して新しいタスクをエンキューしようとします 。offerキューが満杯とすぐに戻っている場合、このメソッドはブロックされませんfalse
  3. 新しいタスクをキューに入れることができない場合(つまり、をoffer返す場合false)は、このタスクを最初のジョブとして、新しいスレッドをスレッドプールに追加しようとします。
  4. 新しいスレッドの追加に失敗した場合、executorはシャットダウンされるか、飽和状態になります。どちらの方法でも、提供されたを使用して新しいタスクが拒否されRejectedExecutionHandlerます。

固定スレッドプールとキャッシュスレッドプールの主な違いは、次の3つの要因に要約されます。

  1. コアプールサイズ
  2. 最大プールサイズ
  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>());
}

ご覧のように:

  • スレッドプールのサイズは固定されています。
  • 需要が高いと成長しません。
  • スレッドがかなりの間アイドル状態である場合、スレッドは縮小しません。
  • これらのすべてのスレッドがいくつかの長期実行タスクで占有されており、到着率がまだかなり高いと想定します。executorは無制限のキューを使用しているため、ヒープの大部分を消費する可能性があります。残念なことに、私たちはを経験するかもしれません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。実際には、スレッドプールは無制限です。
  • アイドル状態のスレッドが1分を超えると、スレッドが終了する場合があります。したがって、スレッドのアイドル状態が多すぎると、プールが縮小する可能性があります。
  • 新しいタスクが入ってくる間に割り当てられたすべてのスレッドが占有されている場合は、新しいスレッドを作成します。新しいタスクを提供するSynchronousQueueことは、反対側に受け入れるタスクがない場合に常に失敗するからです。

どちらを使用すればよいですか?リソース使用率の観点から、どの戦略が優れていますか?

予測可能な短期実行タスクが多数ある場合に使用します。


5

JavaCacheに記載されているように、存続期間の短い非同期タスクがある場合にのみnewCachedThreadPoolを使用する必要があります。処理に時間がかかるタスクを送信すると、スレッドが大量に作成されることになります。newCachedThreadPool(http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/)に長時間実行タスクをより高速で送信すると、CPU使用率が100%になる可能性があります。


1

いくつかの簡単なテストを行ったところ、次のことがわかりました。

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を使用する場合:

スレッドが最小サイズから最大サイズに増加することはありません。つまり、スレッドプールは最小サイズとして固定サイズです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.