web-dev-qa-db-ja.com

ExecutorService、タスクキューがいっぱいになるのを回避する標準的な方法

並行マルチスレッドプログラムを簡単にするためにExecutorServiceを使用しています。次のコードを取ります:

_while(xxx) {
    ExecutorService exService = Executors.newFixedThreadPool(NUMBER_THREADS);
    ...  
    Future<..> ... = exService.submit(..);
    ...
}
_

私の場合、問題は、すべての_NUMBER_THREADS_が使用されている場合、submit()がブロックされないことです。その結果、タスクキューは多くのタスクでいっぱいになります。この結果、ExecutorService.shutdown()を使用して実行サービスをシャットダウンするには時間がかかります(ExecutorService.isTerminated()は長い間falseになります)。理由は、タスクキューがまだ非常にいっぱいであることです。

当面の回避策は、セマフォを使用して、ExecutorServiceのタスクキュー内に多数のエントリが存在することを禁止することです。

_...
Semaphore semaphore=new Semaphore(NUMBER_THREADS);

while(xxx) {
    ExecutorService exService = Executors.newFixedThreadPool(NUMBER_THREADS); 
    ...
    semaphore.aquire();  
    // internally the task calls a finish callback, which invokes semaphore.release()
    // -> now another task is added to queue
    Future<..> ... = exService.submit(..); 
    ...
}
_

より良いカプセル化されたソリューションがあると確信していますか?

30
manuel aldana

トリックは、固定キューサイズを使用することです。

new ThreadPoolExecutor.CallerRunsPolicy()

Guavaの ListeningExecutorService の使用もお勧めします。以下は、コンシューマー/プロデューサーキューの例です。

private ListeningExecutorService producerExecutorService = MoreExecutors.listeningDecorator(newFixedThreadPoolWithQueueSize(5, 20));
private ListeningExecutorService consumerExecutorService = MoreExecutors.listeningDecorator(newFixedThreadPoolWithQueueSize(5, 20));

private static ExecutorService newFixedThreadPoolWithQueueSize(int nThreads, int queueSize) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  5000L, TimeUnit.MILLISECONDS,
                                  new ArrayBlockingQueue<Runnable>(queueSize, true), new ThreadPoolExecutor.CallerRunsPolicy());
}

QoSテクノロジを備えているRabbitMQやActiveMQのようなMQを検討することをお勧めします。

27
Adam Gent

ThreadPoolExecutor.getQueue().size()を呼び出して、待機キューのサイズを確認できます。キューが長すぎる場合は、アクションを実行できます。キューが長すぎてプロデューサーをスローダウンできない場合は、現在のスレッドでタスクを実行することをお勧めします(適切な場合)。

6
Peter Lawrey

ThreadPoolExecutor を自分で作成する方がよい(とにかく、Executors.newXXX()が行うことです)。

コンストラクターでは、Executorがタスクキューとして使用するBlockingQueueを渡すことができます。サイズ制約のあるBlockingQueue( LinkedBlockingQueue など)を渡すと、必要な効果が得られます。

ExecutorService exService = new ThreadPoolExecutor(NUMBER_THREADS, NUMBER_THREADS, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(workQueueSize));
5
Kevin

真のブロッキングThreadPoolExecutorは、多くのウィッシュリストに載っています。JDCバグも開かれています。私は同じ問題に直面していて、これに遭遇しました: http://today.Java.net/pub/a/today/2008/10/23/creating-a-notifying-blocking-thread-pool -executor.html

これはBlockingThreadPoolExecutorの実装であり、RejectionPolicyを使用して実装され、offerを使用してタスクをキューに追加し、キューにスペースができるのを待機します。よさそうです。

5
mdma

サイズが制限されている別のブローキューを追加して、executorServiceの内部キューのサイズを制御することができます。エグゼキュータの前にput()を実行し、タスクを取得してtake()を実行します。 take()はタスクコード内にある必要があります

2
bilal