web-dev-qa-db-ja.com

動的な(拡大/縮小する)スレッドプールの作成

Java(Java.util.concurrent)にスレッドプールを実装する必要があります。アイドル時のスレッド数は最小値であり、ジョブの場合は上限まで増加します(ただしそれ以上になることはありません)。実行が完了するよりも速く送信され、すべてのジョブが完了してそれ以上ジョブが送信されなくなると、下限に戻ります。

そのようなものをどのように実装しますか?これはかなり一般的な使用シナリオになると思いますが、どうやらJava.util.concurrent.Executorsファクトリメソッドは、固定サイズのプールと、多くのジョブが送信されたときに無制限に大きくなるプールしか作成できません。 ThreadPoolExecutorクラスはcorePoolSizeおよびmaximumPoolSizeパラメータを提供しますが、そのドキュメントは、同時にcorePoolSizeを超えるスレッドを持つ唯一の方法を示唆しているようです。制限付きのジョブキューを使用することです。この場合、maximumPoolSizeスレッドに到達すると、自分で対処する必要のあるジョブの拒否が発生しますか?私はこれを思いついた:

//pool creation
ExecutorService pool = new ThreadPoolExecutor(minSize, maxSize, 500, TimeUnit.MILLISECONDS,
    new ArrayBlockingQueue<Runnable>(minSize));
...

//submitting jobs
for (Runnable job : ...) {
    while (true) {
        try {
            pool.submit(job);
            System.out.println("Job " + job + ": submitted");
            break;
        } catch (RejectedExecutionException e) {
            // maxSize jobs executing concurrently atm.; re-submit new job after short wait
            System.out.println("Job " + job + ": rejected...");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e1) {
            }
        }
    }
}

私は何かを見落としていますか?これを行うためのより良い方法はありますか?また、要件によっては、少なくとも(私が思うに)(total number of jobs) - maxSizeジョブが終了するまで上記のコードが終了しないことが問題になる場合があります。したがって、任意の数のジョブをプールに送信して、それらのいずれかが終了するのを待たずにすぐに続行できるようにしたい場合は、管理する専用の「ジョブサミット」スレッドがないとどうすればよいかわかりません。送信されたすべてのジョブを保持するために必要な無制限のキュー。 AFAICS、ThreadPoolExecutor自体に無制限のキューを使用している場合、そのスレッド数がcorePoolSizeを超えることはありません。

16
Olaf Klischat

役立つ可能性のあるトリックの1つは、同じスレッドを使用するRejectedExecutionHandlerを割り当てて、ジョブをブロッキングキューに送信することです。これにより、現在のスレッドがブロックされ、ある種のループが不要になります。

ここで私の答えを参照してください:

処理する必要のあるデータが多すぎる場合にThreadPoolExecutorコマンドを待機させるにはどうすればよいですか?

これがその回答からコピーされた拒否ハンドラーです。

final BlockingQueue queue = new ArrayBlockingQueue<Runnable>(200);
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(nThreads, nThreads,
       0L, TimeUnit.MILLISECONDS, queue);
// by default (unfortunately) the ThreadPoolExecutor will call the rejected
// handler when you submit the 201st job, to have it block you do:
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
   public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
      // this will block if the queue is full
      executor.getQueue().put(r);
   }
});

次に、使用する制限付きブロッキングキューfirstが前にいっぱいになることを認識している限り、コア/最大スレッド数を利用できるはずです。すべてのスレッドはコアスレッドの上に作成されます。したがって、10個のコアスレッドがあり、11番目のジョブで11番目のスレッドを開始する場合は、残念ながら、サイズが0のブロッキングキューが必要になります(おそらくSynchronousQueue)。これは、他の点では優れたExecutorServiceクラスの本当の制限だと思います。

成長と縮小がスレッドと一緒に来るとき、私の頭に浮かぶ名前は1つだけです:Java.util.concurrentのCachedThreadPoolパッケージ。

ExecutorService executor = Executors.newCachedThreadPool();

CachedThreadPool()はスレッドを再利用、および必要に応じて新しいスレッドを作成できます。そして、はい、スレッドが60秒間アイドル状態の場合、CachedThreadPoolはスレッドを強制終了します。したがって、これは非常に軽量です。つまり、成長と縮小を意味します。

9

maximumPoolSizeInteger.MAX_VALUEに設定します。 20億を超えるスレッドがある場合は、幸運を祈ります。

とにかく、ThreadPoolExecutorのJavadocは次のように述べています。

MaximumPoolSizeをInteger.MAX_VALUEなどの本質的に制限のない値に設定することにより、プールが任意の数の同時タスクに対応できるようにします。最も一般的には、コアと最大プールサイズは構築時にのみ設定されますが、setCorePoolSize(int)とsetMaximumPoolSize(int)を使用して動的に変更することもできます。

LinkedBlockingQueueのような同様に無制限のタスクキューでは、これは任意に大きな容量を持つ必要があります。

0
Louis Wasserman