FixedThreadPool vs CachedThreadPool: le moindre de deux maux

93

J'ai un programme qui génère des threads (~ 5-150) qui effectuent un tas de tâches. À l'origine, j'ai utilisé un FixedThreadPoolparce que cette question similaire suggérait qu'ils étaient mieux adaptés pour des tâches plus longues et avec ma connaissance très limitée du multithreading, j'ai considéré la durée de vie moyenne des threads (plusieurs minutes) " longue durée ".

Cependant, j'ai récemment ajouté la possibilité de générer des threads supplémentaires et cela me fait dépasser la limite de thread que j'ai définie. Dans ce cas, serait-il préférable de deviner et d'augmenter le nombre de threads que je peux autoriser ou de passer à un CachedThreadPoolpour ne pas gaspiller de threads?

En les essayant tous les deux au préalable, il ne semble pas y avoir de différence, donc je suis enclin à aller avec le CachedThreadPooljuste pour éviter le gaspillage. Cependant, la durée de vie des threads signifie-t-elle que je devrais plutôt choisir un FixedThreadPoolet traiter uniquement les threads inutilisés? Cette question donne l'impression que ces fils supplémentaires ne sont pas gaspillés, mais j'apprécierais la clarification.

Daniel
la source

Réponses:

108

Un CachedThreadPool est exactement ce que vous devez utiliser pour votre situation car il n'y a aucune conséquence négative à en utiliser un pour les threads de longue durée. Le commentaire dans la documentation java à propos de CachedThreadPools convenant aux tâches courtes suggère simplement qu'ils sont particulièrement appropriés pour de tels cas, non qu'ils ne peuvent ou ne doivent pas être utilisés pour des tâches impliquant des tâches de longue durée.

Pour élaborer davantage, Executors.newCachedThreadPool et Executors.newFixedThreadPool sont tous deux soutenus par la même implémentation de pool de threads (au moins dans le JDK ouvert) avec des paramètres différents. Les différences sont simplement le minimum, le maximum de threads, le temps d'arrêt des threads et le type de file d'attente.

public static ExecutorService newFixedThreadPool(int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
 }

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

Un FixedThreadPool a ses avantages lorsque vous voulez en fait travailler avec un nombre fixe de threads, puisque vous pouvez alors soumettre n'importe quel nombre de tâches au service exécuteur tout en sachant que le nombre de threads sera maintenu au niveau que vous avez spécifié. Si vous souhaitez explicitement augmenter le nombre de threads, ce n'est pas le choix approprié.

Cela signifie cependant que le seul problème que vous pouvez rencontrer avec le CachedThreadPool concerne la limitation du nombre de threads qui s'exécutent simultanément. Le CachedThreadPool ne les limitera pas pour vous, vous devrez donc peut-être écrire votre propre code pour vous assurer que vous n'exécutez pas trop de threads. Cela dépend vraiment de la conception de votre application et de la manière dont les tâches sont soumises au service exécuteur.

Trevor Freeman
la source
1
"Un CachedThreadPool est exactement ce que vous devez utiliser pour votre situation car il n'y a aucune conséquence négative à en utiliser un pour les threads de longue durée". Je ne pense pas être d'accord. CachedThreadPool crée dynamiquement des threads sans limite supérieure. Les tâches de longue durée sur un grand nombre de threads peuvent potentiellement monopoliser toutes les ressources. En outre, avoir plus de threads qu'idéal peut entraîner un gaspillage de ressources trop importantes lors du changement de contexte de ces threads. Bien que vous ayez expliqué à la fin de la réponse que la limitation personnalisée est requise, le début de la réponse est un peu trompeur.
Nishit
1
Pourquoi ne pas simplement créer un ThreadPoolExecutorlike borné ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar
45

Les deux FixedThreadPoolet CachedThreadPoolsont des maux dans les applications très chargées.

CachedThreadPool est plus dangereux que FixedThreadPool

Si votre application est très chargée et nécessite une faible latence, mieux vaut se débarrasser des deux options en raison des inconvénients ci-dessous

  1. Nature illimitée de la file d'attente des tâches: cela peut entraîner un manque de mémoire ou une latence élevée
  2. Les threads longs entraîneront CachedThreadPoolune perte de contrôle lors de la création de threads

Puisque vous savez que les deux sont des maux, le moindre mal ne fait pas de bien. Préférez ThreadPoolExecutor , qui fournit un contrôle granulaire sur de nombreux paramètres.

  1. Définissez la file d'attente des tâches comme file d'attente limitée pour avoir un meilleur contrôle
  2. Have right RejectionHandler - Votre propre RejectionHandler ou Default handlers fournis par JDK
  3. Si vous avez quelque chose à faire avant / après la fin de la tâche, remplacez beforeExecute(Thread, Runnable)etafterExecute(Runnable, Throwable)
  4. Remplacer ThreadFactory si la personnalisation des threads est requise
  5. Contrôle dynamique de la taille du pool de threads au moment de l'exécution (question SE associée: Pool de threads dynamiques )
Ravindra babu
la source
Et si quelqu'un décide d'utiliser commonPool?
Crosk Cool
1
@Ravindra - Vous avez magnifiquement expliqué les inconvénients de CachedThreadPool et FixedThreadPool. Cela montre que vous avez une compréhension approfondie du package d'accès concurrentiel.
Ayaskant
5

J'ai donc un programme qui génère des threads (~ 5-150) qui effectuent un tas de tâches.

Êtes-vous sûr de comprendre comment les threads sont réellement traités par votre système d'exploitation et le matériel de votre choix? Comment Java mappe les threads aux threads du système d'exploitation, comment cela mappe les threads aux threads CPU, etc.? Je demande parce que la création de 150 threads dans ONE JRE n'a de sens que si vous avez d'énormes cœurs / threads de processeur en dessous, ce qui n'est probablement pas le cas. Selon le système d'exploitation et la RAM utilisés, la création de plus de n threads peut même entraîner l'arrêt de votre JRE en raison d'erreurs de MOO. Vous devez donc vraiment faire la distinction entre les threads et le travail à faire par ces threads, combien de travail vous pouvez même traiter, etc.

Et c'est le problème avec CachedThreadPool: il n'a pas de sens de mettre en file d'attente un travail de longue durée dans des threads qui ne peuvent en fait pas s'exécuter car vous n'avez que 2 cœurs de processeur capables de traiter ces threads. Si vous vous retrouvez avec 150 threads planifiés, vous risquez de créer une surcharge inutile pour les planificateurs utilisés dans Java et le système d'exploitation pour les traiter simultanément. C'est tout simplement impossible si vous n'avez que 2 cœurs de processeur, à moins que vos threads n'attendent des E / S ou autre tout le temps. Mais même dans ce cas, beaucoup de threads créeraient beaucoup d'E / S ...

Et ce problème ne se produit pas avec FixedThreadPool, créé avec par exemple 2 + n threads, où n est raisonnablement bas bien sûr, car avec ce matériel et les ressources du système d'exploitation sont utilisés avec beaucoup moins de frais généraux pour gérer les threads qui ne peuvent pas fonctionner de toute façon.

Thorsten Schöning
la source
Parfois, il n'y a pas de meilleur choix, vous pouvez simplement avoir 1 cœur de processeur, mais si vous exécutez un serveur sur lequel chaque demande d'utilisateur déclencherait un thread pour traiter la demande, il n'y aura pas d'autre choix raisonnable, surtout si vous prévoyez pour faire évoluer le serveur une fois que vous augmentez votre base d'utilisateurs.
Michel Feinstein
@mFeinstein Comment ne pas avoir le choix si l'on est en mesure de choisir une implémentation de pool de threads? Dans votre exemple avec 1 cœur de processeur, ne générant que plus de threads n'a tout simplement aucun sens, cela correspond parfaitement à mon exemple en utilisant un FixedThreadPool. Cela évolue également facilement, d'abord avec un ou deux threads de travail, puis avec 10 ou 15 selon le nombre de cœurs.
Thorsten Schöning
2
La grande majorité des implémentations de serveurs Web créeront un nouveau thread pour chaque nouvelle requête HTTP ... Ils ne se soucieront pas du nombre de cœurs réels de la machine, cela rend l'implémentation plus simple et plus facile à mettre à l'échelle. Cela s'applique à de nombreuses autres conceptions dans lesquelles vous souhaitez simplement coder et déployer une seule fois, et ne pas avoir à recompiler et redéployer si vous modifiez la machine, qui pourrait être une instance cloud.
Michel Feinstein
@mFeinstein La plupart des serveurs Web utilisent eux-mêmes des pools de threads pour les requêtes, simplement parce que générer des threads qui ne peuvent pas s'exécuter n'a pas de sens, ou ils utilisent des boucles d'événements pour les connexions et traitent les requêtes dans des pools par la suite ou autre. De plus, vous manquez le point, à savoir que la question est de savoir si quelqu'un est capable de choisir le pool de threads correct et de générer des threads qui ne peuvent pas s'exécuter de toute façon n'a toujours pas de sens. Un FixedthreadPool configuré pour une quantité raisonnable de threads par machine en fonction des échelles des cœurs très bien.
Thorsten Schöning
3
@ ThorstenSchöning, avoir 50 threads liés au processeur sur une machine à 2 cœurs ne sert à rien. Avoir 50 threads liés aux E / S sur une machine à 2 cœurs peut être très utile.
Paul Draper