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


127

作成できるスレッドの数に制限を設けて、キャッシュされたスレッドプールを作成することは不可能のようです。

静的な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の実行メソッドのバグですか?または、これは意図的なものですか?または他の方法がありますか?

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


1
あなたの質問は何ですか?あなたの2番目のコード例はあなたのタイトルへの答えではありませんか?
rsp

4
タスクの数が増えるにつれてオンデマンドでスレッドを追加しますが、スレッドの最大数を超えるスレッドプールは追加しません。CachedThreadPoolはすでにこれを行っていますが、無制限の数のスレッドを追加し、事前定義されたサイズで停止しない点が異なります。例で定義するサイズは3です。2番目の例では、1つのスレッドを追加しますが、他のタスクがまだ完了していない間に新しいタスクが到着しても、2つは追加しません。
Matt Crinklaw-Vogt

これをチェックして解決します、debuggingisfun.blogspot.com
2012/05 /…

回答:


235

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

補遺:これはかなり古い回答です。コアサイズが0になると、JDKの動作が変更されたようです。JDK1.6以降、コアサイズが0でプールにスレッドがない場合、ThreadPoolExecutorはそのタスクを実行するスレッド。したがって、コアサイズ0は上記のルールの例外です。おかげでスティーブのために持って来る私の注意にそれを。


4
allowCoreThreadTimeOutこの答えを完璧にするために、メソッドについて少し説明する必要があります。@ user1046052の回答を見る
hsestupin

1
正解です。追加するポイントは1つだけです。他の拒否ポリシーについても言及する価値があります。@brianeggeの回答を見る
ジェフ

1
動作2はmaxThreadサイズに達し、アイドルスレッドがない場合、タスクをキューに入れると言うべきではありません
ゾルタン

1
キューのサイズが何を意味するかについて詳しく説明していただけますか?拒否される前にキューに入れることができるタスクは20だけということですか?
ゾルタン

1
@Zoltán私はこれを少し前に書いたので、それ以来いくつかの動作が変わった可能性があります(私は最新のアクティビティをあまり詳しく追っていませんでした)が、これらの動作が変更されていないと仮定すると、#2は述べたとおり正しいです。おそらくこれの最も重要な(そして多少意外な)ポイントです。コアサイズに達すると、TPEは新しいスレッドの作成よりもキューイングを優先します。キューのサイズは、文字通りTPEに渡されるキューのサイズです。キューがいっぱいになっても、最大サイズに達していない場合は、新しいスレッドを作成します(タスクを拒否しません)。#3を参照してください。お役に立てば幸いです。
sjlee 2016

60

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

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

1
あなたは正しいです。その方法はjdk 1.6で追加されたので、多くの人がそれについて知っているわけではありません。また、残念ながら「最小」のコアプールサイズを使用することはできません。
jtahlborn

4
これに関する私の唯一の心配は(JDK 8のドキュメントから):「メソッドexecute(Runnable)で新しいタスクが送信され、実行中のcorePoolSizeスレッドより少ない場合、他のワーカーがリクエストを処理するために新しいスレッドが作成され、リクエストを処理します。スレッドはアイドル状態です。」
veeeee 2016

これが実際に機能しないことを確認してください。前回私が上記を実行しているのを見たとき、5をスポーンしたとしても、実際には1つのスレッドでのみ作業が実行されます。ここでも数年ですが、ThreadPoolExecutorの実装に取り​​掛かると、キューがいっぱいになると、新しいスレッドにのみディスパッチされます。無制限のキューを使用すると、これが発生することはありません。作業を送信してスレッド名をログに記録してから、スリープすることでテストできます。すべてのrunnableは同じ名前を出力することになり、他のスレッドでは実行されません。
Matt Crinklaw-Vogt 2016

2
これは機能します、マット。コアサイズを0に設定したため、スレッドが1つしかありませんでした。ここでのコツは、コアサイズを最大サイズに設定することです。
T-Gergely

1
@vegeeは正しい-これは実際にはあまり機能しない-ThreadPoolExecutorは、corePoolSizeを超えた場合にのみスレッドを再利用します。したがって、corePoolSizeがmaxPoolSizeと等しい場合、プールがいっぱいになったときにのみスレッドキャッシュのメリットが得られます(これを使用する予定であるが、通常は最大プールサイズを下回らない場合は、スレッドタイムアウトを低くすることもできます)値;そしてキャッシングがないことに注意してください-常に新しいスレッド)
Chris Riddell

7

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

これはドキュメントに明確に記述されています。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()とスローされます。最初にメモリ不足になるか、これが発生するかは不明です。RejectedExecutionExceptionInteger.MAX_VALUE

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

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


また、 'allowCoreThreadTimeOut'を 'true'に設定する必要があると思います。それ以外の場合は、スレッドが作成されると、永久に保持されます。gist.github.com
eric

おっと私はそれを逃したばかりです、申し訳ありませんが、あなたの答えは完璧です!
エリック2014

6

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

CallerRunsPolicy
AbortPolicy
DiscardPolicy
DiscardOldestPolicy

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


5

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

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

これにより、ThreadPoolExecutor5から始まり、最大10の同時実行スレッドをCallerRunsPolicy実行に使用するが作成されます。


このソリューションの問題は、数またはプロデューサーを増やすと、バックグラウンドスレッドを実行するスレッドの数が増えることです。多くの場合、それはあなたが望むものではありません。
グレー

3

ThreadPoolExecutorのJavadocによると、

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

(エンファシス鉱山。)

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


2

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


私はあなたがサイズ0(デフォルト)を意味すると思うので、キューに入れられたタスクはなく、executorserviceに毎回新しいスレッドを作成することを強制します。
Leonmax 2014年

2

メソッド/プロパティの多くがプライベートであるため、たとえばPooledExecutorServiceからサブクラス化したとしても、答えのいずれかが実際に質問に答えているかのようには見えません。以下をせよ:

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);
         }
    };
 };

上記のテストされていないps


2

これが別の解決策です。私はこのソリューションがあなたが望むように動作すると思います(このソリューションを誇りに思っていませんが):

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);

2

これはあなたが望んでいることです(少なくとも私はそう思います)。説明については、Jonathan Feinbergの回答を確認してください

Executors.newFixedThreadPool(int n)

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


4
もちろん、固定スレッドプールを使用することはできますが、nスレッドを永久に、またはシャットダウンを呼び出すまで残しておくことができます。キャッシュされたスレッドプールとまったく同じものが必要です(オンデマンドでスレッドを作成し、タイムアウト後にそれらを強制終了します)。ただし、作成できるスレッドの数に制限があります。
Matt Crinklaw-Vogt

0
  1. @sjleeのThreadPoolExecutor提案に従って使用できます

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

    動的スレッドプール

    または

  2. Java 8で導入されたnewWorkStealingPool API を使用できます。

    public static ExecutorService newWorkStealingPool()

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

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


0

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

キャッシュされたスレッドプールとまったく同じように(オンデマンドでスレッドを作成し、タイムアウト後にそれらを強制終了します)、作成できるスレッドの数に制限があり、追加のタスクをキューに入れたらキューに入れ続けることができますスレッド制限。

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

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

SynchronousQueueは、定義上、要素を保持できないため、制限3に達しても、タスクはキューに入れられません。

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

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

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

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

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


0

これは、Java8 +(およびその他、現時点では..)で機能します。

     Executor executor = new ThreadPoolExecutor(3, 3, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>()){{allowCoreThreadTimeOut(true);}};

3はスレッド数の制限、5はアイドルスレッドのタイムアウトです。

それが自分で機能するかどうか確認したい場合は、次のコードでジョブを実行します。

public static void main(String[] args) throws InterruptedException {
    final int DESIRED_NUMBER_OF_THREADS=3; // limit of number of Threads for the task at a time
    final int DESIRED_THREAD_IDLE_DEATH_TIMEOUT=5; //any idle Thread ends if it remains idle for X seconds

    System.out.println( java.lang.Thread.activeCount() + " threads");
    Executor executor = new ThreadPoolExecutor(DESIRED_NUMBER_OF_THREADS, DESIRED_NUMBER_OF_THREADS, DESIRED_THREAD_IDLE_DEATH_TIMEOUT, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>()) {{allowCoreThreadTimeOut(true);}};

    System.out.println(java.lang.Thread.activeCount() + " threads");

    for (int i = 0; i < 5; i++) {
        final int fi = i;
        executor.execute(() -> waitsout("starting hard thread computation " + fi, "hard thread computation done " + fi,2000));
    }
    System.out.println("If this is UP, it works");

    while (true) {
        System.out.println(
                java.lang.Thread.activeCount() + " threads");
        Thread.sleep(700);
    }

}

static void waitsout(String pre, String post, int timeout) {
    try {
        System.out.println(pre);
        Thread.sleep(timeout);
        System.out.println(post);
    } catch (Exception e) {
    }
}

私のための上記のコードの出力は

1 threads
1 threads
If this is UP, it works
starting hard thread computation 0
4 threads
starting hard thread computation 2
starting hard thread computation 1
4 threads
4 threads
hard thread computation done 2
hard thread computation done 0
hard thread computation done 1
starting hard thread computation 3
starting hard thread computation 4
4 threads
4 threads
4 threads
hard thread computation done 3
hard thread computation done 4
4 threads
4 threads
4 threads
4 threads
3 threads
3 threads
3 threads
1 threads
1 threads
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.