web-dev-qa-db-ja.com

サイズ制限のあるキャッシュスレッドプールを作成することはできませんか?

作成できるスレッドの数に制限があるキャッシュスレッドプールを作成することは不可能のようです。

静的Executors.newCachedThreadPoolが標準のJavaライブラリに実装される方法は次のとおりです。

 public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

したがって、そのテンプレートを使用して、固定サイズのキャッシュスレッドプールを作成します。

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new SynchronusQueue<Runable>());

これを使用して3つのタスクを送信すると、すべてが正常になります。さらにタスクを送信すると、実行例外が拒否されます。

これを試してください:

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runable>());

すべてのスレッドが順番に実行されます。つまり、スレッドプールは、タスクを処理するために複数のスレッドを作成することはありません。

これは、ThreadPoolExecutorのexecuteメソッドのバグですか?それとも意図的なものですか?または他の方法がありますか?

編集:キャッシュされたスレッドプールとまったく同じものが必要です(要求に応じてスレッドを作成し、タイムアウト後にスレッドを殺します)が、作成できるスレッドの数に制限があり、一度追加されたタスクをキューに入れ続けることができますスレッド制限に達しました。 sjleeの回答によると、これは不可能です。 ThreadPoolExecutorのexecute()メソッドを見ると、実際に不可能です。 ThreadPoolExecutorをサブクラス化し、SwingWorkerのようにexecute()をオーバーライドする必要がありますが、SwingWorkerがexecute()で行うことは完全なハックです。

117

ThreadPoolExecutorには次のいくつかの主要な動作があり、これらの動作によって問題を説明できます。

タスクが送信されると、

  1. スレッドプールがコアサイズに達していない場合、新しいスレッドが作成されます。
  2. コアサイズに達し、アイドルスレッドがない場合、タスクをキューに入れます。
  3. コアサイズに達すると、アイドルスレッドがなくなり、キューがいっぱいになり、新しいスレッドが作成されます(最大サイズに達するまで)。
  4. 最大サイズに達した場合、アイドルスレッドはなく、キューはいっぱいになり、拒否ポリシーが開始されます。

最初の例では、SynchronousQueueのサイズは基本的に0であることに注意してください。したがって、最大サイズ(3)に達すると、拒否ポリシーが有効になります(#4)。

2番目の例では、選択できるキューは無制限のサイズを持つLinkedBlockingQueueです。したがって、動作#2で動けなくなります。

キャッシュされた型や固定された型は、動作がほぼ完全に決定されるため、実際にはあまりいじることはできません。

制限された動的なスレッドプールが必要な場合は、正のコアサイズと最大サイズを有限サイズのキューと組み合わせて使用​​する必要があります。例えば、

new ThreadPoolExecutor(10, // core size
    50, // max size
    10*60, // idle timeout
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<Runnable>(20)); // queue with a size

補遺:これはかなり古い答えであり、JDKはコアサイズが0になると動作を変更したようです。JDK1.6以降、コアサイズが0で、プールにスレッドがない場合、ThreadPoolExecutorはそのタスクを実行するスレッドを追加します。したがって、コアサイズ0は、上記の規則の例外です。ありがとう Steve for bringing それは私の注意です。

223
sjlee

何かを見逃していない限り、元の質問に対する解決策は簡単です。次のコードは、元のポスターで説明されているとおり、目的の動作を実装します。無制限のキューで動作する最大5つのスレッドが生成され、アイドルスレッドは60秒後に終了します。

tp = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<Runnable>());
tp.allowCoreThreadTimeOut(true);
58
user1046052

同じ問題があった。 他の答えはすべての問題をまとめるものではないので、私は私のものを追加しています:

docs で明確に記述されています:最大スレッド設定をブロックしない(LinkedBlockingQueue)キュー設定を使用しても効果がない場合、コアスレッドのみが使用されます。

そう:

_public class MyExecutor extends ThreadPoolExecutor {

    public MyExecutor() {
        super(4, 4, 5,TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        allowCoreThreadTimeOut(true);
    }

    public void setThreads(int n){
        setMaximumPoolSize(Math.max(1, n));
        setCorePoolSize(Math.max(1, n));
    }

}
_

このエグゼキューターには次のものがあります。

  1. 制限のないキューを使用しているため、最大スレッドという概念はありません。このようなキューは、エグゼキューターが通常のポリシーに従っている場合、エクゼキューターが大量の非コアの余分なスレッドを作成する可能性があるため、これは良いことです。

  2. 最大サイズ_Integer.MAX_VALUE_のキュー。 Submit()は、保留中のタスクの数が_Integer.MAX_VALUE_を超える場合、RejectedExecutionExceptionをスローします。最初にメモリ不足になるか、これが起こるかはわかりません。

  3. 4つのコアスレッドが可能です。アイドルコアスレッドは、5秒間アイドル状態になると自動的に終了します。したがって、厳密にオンデマンドスレッドです。番号はsetThreads()メソッドを使用して変更できます。

  4. コアスレッドの最小数が1未満にならないようにします。そうしないと、submit()がすべてのタスクを拒否します。コアスレッドは> =最大スレッドである必要があるため、メソッドsetThreads()は最大スレッドも設定しますが、制限のないキューでは最大スレッド設定は役に立ちません。

7
S.D.

最初の例では、AbortPolicyがデフォルトのRejectedExecutionHandlerであるため、後続のタスクは拒否されます。 ThreadPoolExecutorには次のポリシーが含まれており、setRejectedExecutionHandlerメソッドを使用して変更できます。

CallerRunsPolicy
AbortPolicy
DiscardPolicy
DiscardOldestPolicy

CallerRunsPolicyでキャッシュされたスレッドプールが必要なようです。

6
brianegge

ここでの答えはどれも、ApacheのHTTPクライアント(3.xバージョン)を使用して、限られた量のHTTP接続を作成することに関する問題を解決しませんでした。良いセットアップを見つけるのに数時間かかったので、共有します:

private ExecutorService executor = new ThreadPoolExecutor(5, 10, 60L,
  TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
  Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

これにより、5で始まり、ThreadPoolExecutorを使用して同時に実行される最大10個のスレッドを実行に保持するCallerRunsPolicyが作成されます。

5
paul

ThreadPoolExecutorのJavadocごと:

CorePoolSizeを超え、maximumPoolSizeスレッドよりも少ないスレッドが実行されている場合、新しいスレッドが作成されますキューがいっぱいの場合のみ。 corePoolSizeとmaximumPoolSizeを同じに設定することにより、固定サイズのスレッドプールを作成します。

(エンファシス鉱山。)

ジッタの答えはあなたが望むものですが、私の答えは他の質問に答えます。 :)

3

もう1つのオプションがあります。新しいSynchronousQueueを使用する代わりに、他のキューも使用できますが、そのサイズが1であることを確認する必要があります。これにより、executorserviceが新しいスレッドを作成します。

2
Ashkrit

これはあなたが望むものです(少なくとも私はそう思います)。説明を確認するには Jonathan Feinberg answer

Executors.newFixedThreadPool(int n)

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

2
jitter

メソッド/プロパティの多くはプライベートであるため、PooledExecutorServiceのサブクラスであっても、実際に質問に答えているかのように見えません-実際にはこれを行う方法がわかりませんaddIfUnderMaximumPoolSizeが保護されていると、次のことができます。

class MyThreadPoolService extends ThreadPoolService {
    public void execute(Runnable run) {
        if (poolSize() == 0) {
            if (addIfUnderMaximumPoolSize(run) != null)
                return;
        }
        super.execute(run);
    }
}

私が得た最も近いものはこれでした-しかし、それでも非常に良い解決策ではありません

new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) {
    public void execute(Runnable command) {
        if (getPoolSize() == 0 && getActiveCount() < getMaximumPoolSize()) {        
            super.setCorePoolSize(super.getCorePoolSize() + 1);
        }
        super.execute(command);
    }

    protected void afterExecute(Runnable r, Throwable t) {
         // nothing in the queue
         if (getQueue().isEmpty() && getPoolSize() > min) {
             setCorePoolSize(getCorePoolSize() - 1);
         }
    };
 };

追伸上記をテストしていません

2
Stuart

別のソリューションがあります。私はこのソリューションがあなたが望むように振る舞うと思う(このソリューションを誇りに思っているわけではないが):

final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    public boolean offer(Runnable o) {
        if (size() > 1)
            return false;
        return super.offer(o);
    };

    public boolean add(Runnable o) {
        if (super.offer(o))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
};

RejectedExecutionHandler handler = new RejectedExecutionHandler() {         
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        queue.add(r);
    }
};

dbThreadExecutor =
        new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, queue, handler);
1
Stuart

問題は次のように要約されました。

キャッシュされたスレッドプール(要求に応じてスレッドを作成し、タイムアウト後にスレッドを削除する)のようなものが必要ですが、作成できるスレッドの数に制限があり、ヒットした後は追加のタスクをキューに入れ続けることができますスレッド制限。

ソリューションを指す前に、次のソリューションが機能しない理由を説明します。

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());

SynchronousQueueは定義上、要素を保持できないため、これは3の制限に達するとタスクをキューに入れません。

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

ThreadPoolExecutorは、キューがいっぱいの場合にcorePoolSizeを超えるスレッドのみを作成するため、これは単一のスレッドのみを作成しません。ただし、LinkedBlockingQueueがいっぱいになることはありません。

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<Runnable>());
executor.allowCoreThreadTimeOut(true);

ThreadPoolExecutorは、既存のスレッドがアイドル状態であってもcorePoolSizeに達するまでスレッド数を増やすため、corePoolSizeに達するまでスレッドを再利用しません。この欠点に耐えることができる場合、これが問題の最も簡単な解決策です。また、「実践におけるJava並行性」(p172の脚注)で説明されているソリューションでもあります。

この問題に対する唯一の完全な解決策は、キューのofferメソッドをオーバーライドし、この質問への回答で説明されているようにRejectedExecutionHandlerを書くことを含むものであるようです: Queueする前にThreadPoolExecutorを取得してスレッドを最大に増やす方法は?

0
  1. @ sjleeで示唆されているように、ThreadPoolExecutorを使用できます。

    プールのサイズを動的に制御できます。詳細については、この質問をご覧ください。

    動的スレッドプール

    OR

  2. newWorkStealingPool APIを使用できます。これは、Java 8。

    public static ExecutorService newWorkStealingPool()
    

    使用可能なすべてのプロセッサーをそのターゲット並列処理レベルとして使用して、ワークスチールスレッドプールを作成します。

デフォルトでは、並列処理レベルはサーバーのCPUコアの数に設定されています。 4つのコアCPUサーバーがある場合、スレッドプールサイズは4になります。このAPIはForkJoinPoolタイプのExecutorServiceを返し、ForkJoinPoolでビジースレッドからタスクをスチールすることでアイドルスレッドのワークスチールを許可します。

0
Ravindra babu