web-dev-qa-db-fra.com

ExecutorService, moyen standard d'éviter que la file d'attente des tâches ne soit trop pleine

J'utilise ExecutorService pour faciliter le programme simultané multithread. Prenez le code suivant:

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

Dans mon cas, le problème est que submit() ne bloque pas si tous NUMBER_THREADS Sont occupés. La conséquence est que la file d'attente des tâches est inondée par de nombreuses tâches. La conséquence de cela est que l'arrêt du service d'exécution avec ExecutorService.shutdown() prend du temps (ExecutorService.isTerminated() sera faux pendant longtemps). La raison en est que la file d'attente des tâches est encore assez pleine.

Pour l'instant, ma solution consiste à travailler avec des sémaphores pour interdire d'avoir trop d'entrées dans la file d'attente des tâches de 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(..); 
    ...
}

Je suis sûr qu'il existe une meilleure solution plus encapsulée?

30
manuel aldana

L'astuce consiste à utiliser une taille de file d'attente fixe et:

new ThreadPoolExecutor.CallerRunsPolicy()

Je recommande également d'utiliser Guava's ListeningExecutorService . Voici un exemple de files d'attente consommateur/producteur.

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

Quoi de mieux et vous voudrez peut-être envisager un MQ comme RabbitMQ ou ActiveMQ car ils ont la technologie QoS.

27
Adam Gent

Vous pouvez appeler ThreadPoolExecutor.getQueue().size() pour connaître la taille de la file d'attente. Vous pouvez entreprendre une action si la file d'attente est trop longue. Je suggère d'exécuter la tâche dans le thread actuel si la file d'attente est trop longue pour ralentir le producteur (si cela est approprié).

6
Peter Lawrey

Il vaut mieux créer vous-même le ThreadPoolExecutor (ce que fait de toute façon Executors.newXXX ()).

Dans le constructeur, vous pouvez passer une BlockingQueue pour que l'exécuteur l'utilise comme file d'attente de tâches. Si vous passez une BlockingQueue contrainte par la taille (comme LinkedBlockingQueue ), elle devrait obtenir l'effet souhaité.

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

Un véritable ThreadPoolExecutor bloquant était sur la liste de souhaits de nombreux, il y a même un bogue JDC ouvert dessus. Je suis confronté au même problème et suis tombé sur ceci: http://today.Java.net/pub/a/today/2008/10/23/creating-a-notifying-blocking-thread-pool -executor.html

Il s'agit d'une implémentation d'un BlockingThreadPoolExecutor, implémenté à l'aide d'un RejectionPolicy qui utilise l'offre pour ajouter la tâche à la file d'attente, en attendant que la file d'attente ait de la place. Ça à l'air bon.

5
mdma

vous pouvez ajouter une autre file d'attente bloquante qui a une taille limitée pour contrôler la taille de la file d'attente interne dans executorService, certains pensent comme un sémaphore mais très facile. avant l'exécuteur testamentaire vous mettez () et quand la tâche est accomplie take (). take () doit être à l'intérieur du code de tâche

2
bilal