Executors.newCachedThreadPool () et Executors.newFixedThreadPool ()


Réponses:


202

Je pense que la documentation explique assez bien la différence et l'utilisation de ces deux fonctions:

newFixedThreadPool

Crée un pool de threads qui réutilise un nombre fixe de threads fonctionnant à partir d'une file d'attente partagée illimitée. À tout moment, la plupart des threads nThreads seront des tâches de traitement actives. Si des tâches supplémentaires sont soumises lorsque tous les threads sont actifs, ils attendront dans la file d'attente jusqu'à ce qu'un thread soit disponible. Si un thread se termine en raison d'un échec lors de l'exécution avant l'arrêt, un nouveau prendra sa place si nécessaire pour exécuter les tâches suivantes. Les threads du pool existeront jusqu'à ce qu'il soit explicitement arrêté.

newCachedThreadPool

Crée un pool de threads qui crée de nouveaux threads selon les besoins, mais réutilise les threads précédemment construits lorsqu'ils sont disponibles. Ces pools améliorent généralement les performances des programmes qui exécutent de nombreuses tâches asynchrones de courte durée. Les appels à exécuter réutiliseront les threads précédemment construits s'ils sont disponibles. Si aucun thread existant n'est disponible, un nouveau thread sera créé et ajouté au pool. Les threads qui n'ont pas été utilisés pendant soixante secondes sont arrêtés et supprimés du cache. Ainsi, un pool qui reste inactif suffisamment longtemps ne consommera aucune ressource. Notez que des pools avec des propriétés similaires mais des détails différents (par exemple, des paramètres de délai d'expiration) peuvent être créés à l'aide des constructeurs ThreadPoolExecutor.

En termes de ressources, le newFixedThreadPoolmaintiendra tous les threads en cours d'exécution jusqu'à ce qu'ils soient explicitement terminés. Dans les newCachedThreadPoolthreads qui n'ont pas été utilisés pendant soixante secondes sont arrêtés et supprimés du cache.

Compte tenu de cela, la consommation de ressources dépendra beaucoup de la situation. Par exemple, si vous avez un grand nombre de tâches de longue durée, je suggérerais le FixedThreadPool. En ce qui concerne le CachedThreadPool, la documentation dit que "ces pools amélioreront généralement les performances des programmes qui exécutent de nombreuses tâches asynchrones de courte durée".


1
oui, j'ai parcouru la documentation ... le problème est ... fixedThreadPool provoque une erreur de mémoire insuffisante @ 3 threads .. où comme cachedPool crée en interne un seul thread ... en augmentant la taille du tas, j'obtiens le même la performance pour les deux ... y a-t-il autre chose qui me manque !!
hakish le

1
Fournissez-vous une Threadfactory au ThreadPool? Je suppose que cela pourrait stocker un état dans les threads qui ne sont pas collectés. Sinon, peut-être que votre programme s'exécute si près de la taille limite du tas qu'avec la création de 3 threads, il provoque un OutOfMemory. De plus, si cachedPool crée en interne un seul thread, cela indique que vos tâches sont en cours d'exécution synchronisées.
bruno conde le

@brunoconde Tout comme @Louis F. le souligne, cela newCachedThreadPoolpeut causer de sérieux problèmes car vous laissez tout le contrôle au thread poolet lorsque le service fonctionne avec d'autres dans le même hôte , ce qui peut provoquer un crash des autres en raison d'une attente prolongée du processeur. Je pense donc que cela newFixedThreadPoolpeut être plus sûr dans ce genre de scénario. Cet article clarifie également les différences les plus marquantes entre eux.
Hearen

75

Pour compléter les autres réponses, je voudrais citer Effective Java, 2e édition, par Joshua Bloch, chapitre 10, point 68:

"Le choix du service exécuteur pour une application particulière peut être délicat. Si vous écrivez un petit programme ou un serveur peu chargé , utiliser Executors.new- CachedThreadPool est généralement un bon choix , car il ne nécessite aucune configuration et généralement" bonne chose." Mais un pool de threads mis en cache n'est pas un bon choix pour un serveur de production fortement chargé !

Dans un pool de threads mis en cache , les tâches soumises ne sont pas mises en file d'attente mais immédiatement transmises à un thread pour exécution. Si aucun thread n'est disponible, un nouveau est créé . Si un serveur est tellement chargé que tous ses processeurs sont pleinement utilisés et que davantage de tâches arrivent, plus de threads seront créés, ce qui ne fera qu'empirer les choses.

Par conséquent, dans un serveur de production très chargé , il est préférable d'utiliser Executors.newFixedThreadPool , qui vous donne un pool avec un nombre fixe de threads, ou d'utiliser directement la classe ThreadPoolExecutor, pour un contrôle maximal. "


15

Si vous regardez le code source , vous verrez, ils appellent ThreadPoolExecutor. en interne et définir leurs propriétés. Vous pouvez créer le vôtre pour avoir une meilleure maîtrise de vos besoins.

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

1
Exactement, un exécuteur de thread mis en cache avec une limite supérieure raisonnable et disons 5 à 10 minutes de récolte inactive est tout simplement parfait pour la plupart des occasions.
Agoston Horvath

12

Si vous n'êtes pas préoccupé par une file d'attente illimitée de tâches appelables / exécutables , vous pouvez utiliser l'une d'entre elles. Comme suggéré par bruno, je préfère aussi newFixedThreadPoolà newCachedThreadPoolplus de ces deux.

Mais ThreadPoolExecutor offre des fonctionnalités plus flexibles par rapport à l'un newFixedThreadPoolou l' autrenewCachedThreadPool

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

Avantages:

  1. Vous avez un contrôle total sur la taille de BlockingQueue . Ce n'est pas illimité, contrairement aux deux options précédentes. Je n'obtiendrai pas une erreur de mémoire insuffisante en raison d'un énorme tas de tâches appelables / exécutables en attente lorsqu'il y a des turbulences inattendues dans le système.

  2. Vous pouvez mettre en œuvre une stratégie de gestion des rejets personnalisée OU utiliser l'une des stratégies:

    1. Par défaut ThreadPoolExecutor.AbortPolicy, le gestionnaire lève une exception RejectedExecutionException lors du rejet.

    2. Dans ThreadPoolExecutor.CallerRunsPolicy, le thread qui invoque s'exécute lui-même exécute la tâche. Cela fournit un mécanisme de contrôle de rétroaction simple qui ralentira la vitesse à laquelle de nouvelles tâches sont soumises.

    3. Dans ThreadPoolExecutor.DiscardPolicy, une tâche qui ne peut pas être exécutée est simplement supprimée.

    4. Dans ThreadPoolExecutor.DiscardOldestPolicy, si l'exécuteur n'est pas arrêté, la tâche en tête de la file d'attente de travail est supprimée, puis l'exécution est relancée (ce qui peut échouer à nouveau, provoquant la répétition de cette opération.)

  3. Vous pouvez implémenter une fabrique de threads personnalisée pour les cas d'utilisation ci-dessous:

    1. Pour définir un nom de thread plus descriptif
    2. Pour définir l'état du démon de thread
    3. Pour définir la priorité des threads

11

C'est vrai, ce Executors.newCachedThreadPool()n'est pas un excellent choix pour le code serveur qui traite plusieurs clients et demandes simultanées.

Pourquoi? Il y a essentiellement deux problèmes (liés) avec cela:

  1. C'est illimité, ce qui signifie que vous ouvrez la porte à quiconque pour paralyser votre JVM en injectant simplement plus de travail dans le service (attaque DoS). Les threads consomment une quantité non négligeable de mémoire et augmentent également la consommation de mémoire en fonction de leur travail en cours, il est donc assez facile de renverser un serveur de cette façon (sauf si vous avez d'autres disjoncteurs en place).

  2. Le problème illimité est exacerbé par le fait que l'exécuteur est dirigé par un, SynchronousQueuece qui signifie qu'il y a un transfert direct entre le donneur de tâches et le pool de threads. Chaque nouvelle tâche créera un nouveau thread si tous les threads existants sont occupés. C'est généralement une mauvaise stratégie pour le code serveur. Lorsque le processeur est saturé, les tâches existantes prennent plus de temps à se terminer. Pourtant, plus de tâches sont soumises et plus de threads créés, de sorte que les tâches prennent de plus en plus de temps à terminer. Lorsque le processeur est saturé, plus de threads n'est certainement pas ce dont le serveur a besoin.

Voici mes recommandations:

Utilisez un pool de threads de taille fixe Executors.newFixedThreadPool ou un ThreadPoolExecutor. avec un nombre maximum de threads défini;


6

La ThreadPoolExecutorclasse est l'implémentation de base des exécuteurs renvoyés par de nombreuses Executorsméthodes de fabrique. Alors abordons les pools de threads fixes et mis en cache du ThreadPoolExecutorpoint de vue de.

ThreadPoolExecutor

Le constructeur principal de cette classe ressemble à ceci:

public ThreadPoolExecutor(
                  int corePoolSize,
                  int maximumPoolSize,
                  long keepAliveTime,
                  TimeUnit unit,
                  BlockingQueue<Runnable> workQueue,
                  ThreadFactory threadFactory,
                  RejectedExecutionHandler handler
)

Taille de la piscine principale

Le corePoolSizedétermine la taille minimale du pool de threads cible. L'implémentation conserverait un pool de cette taille même s'il n'y a aucune tâche à exécuter.

Taille maximale de la piscine

Le maximumPoolSizeest le nombre maximum de threads qui peuvent être actifs à la fois.

Une fois que le pool de threads augmente et devient plus grand que le corePoolSizeseuil, l'exécuteur peut mettre fin aux threads inactifs et atteindre à corePoolSizenouveau. Si allowCoreThreadTimeOutest vrai, l'exécuteur peut même mettre fin aux threads du pool de base s'ils étaient inactifs au-delà du keepAliveTimeseuil.

Donc, l'essentiel est que si les threads restent inactifs au-delà du keepAliveTimeseuil, ils peuvent se terminer car il n'y a pas de demande pour eux.

File d'attente

Que se passe-t-il lorsqu'une nouvelle tâche arrive et que tous les threads principaux sont occupés? Les nouvelles tâches seront mises en file d'attente dans cette BlockingQueue<Runnable>instance. Lorsqu'un thread devient libre, l'une de ces tâches en file d'attente peut être traitée.

Il existe différentes implémentations de l' BlockingQueueinterface en Java, nous pouvons donc implémenter différentes approches de mise en file d'attente telles que:

  1. File d'attente limitée : les nouvelles tâches seraient mises en file d'attente dans une file d'attente de tâches limitée.

  2. File d'attente illimitée : les nouvelles tâches seraient mises en file d'attente dans une file d'attente de tâches illimitée. Ainsi, cette file d'attente peut augmenter autant que la taille du tas le permet.

  3. Transfert synchrone : nous pouvons également utiliser SynchronousQueuepour mettre en file d'attente les nouvelles tâches. Dans ce cas, lors de la mise en file d'attente d'une nouvelle tâche, un autre thread doit déjà attendre cette tâche.

Soumission de travail

Voici comment ThreadPoolExecutorexécute une nouvelle tâche:

  1. Si moins de corePoolSizethreads sont en cours d'exécution, essaie de démarrer un nouveau thread avec la tâche donnée comme premier travail.
  2. Sinon, il essaie de mettre la nouvelle tâche en file d'attente à l'aide de la BlockingQueue#offerméthode. La offerméthode ne bloquera pas si la file d'attente est pleine et retourne immédiatement false.
  3. S'il ne parvient pas à mettre en file d'attente la nouvelle tâche (c'est-à-dire offerretourne false), il essaie alors d'ajouter un nouveau thread au pool de threads avec cette tâche comme premier travail.
  4. S'il ne parvient pas à ajouter le nouveau thread, l'exécuteur est soit arrêté soit saturé. Dans tous les cas, la nouvelle tâche serait rejetée en utilisant le fichier RejectedExecutionHandler.

La principale différence entre les pools de threads fixes et mis en cache se résume à ces trois facteurs:

  1. Taille de la piscine principale
  2. Taille maximale de la piscine
  3. File d'attente
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Type de piscine | Taille du noyau | Taille maximale | Stratégie de file d'attente |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Fixe | n (fixe) | n (fixe) | Unbounded `LinkedBlockingQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| En cache | 0 | Integer.MAX_VALUE | `SynchronousQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +


Pool de threads fixe


Voici comment ça Excutors.newFixedThreadPool(n)marche:

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

Comme vous pouvez le voir:

  • La taille du pool de threads est fixe.
  • S'il y a une forte demande, elle ne croîtra pas.
  • Si les threads sont inactifs pendant un certain temps, ils ne rétréciront pas.
  • Supposons que tous ces threads soient occupés par des tâches de longue durée et que le taux d'arrivée soit encore assez élevé. Étant donné que l'exécuteur utilise une file d'attente illimitée, il peut consommer une grande partie du tas. Étant assez malheureux, nous pouvons éprouver un OutOfMemoryError.

Quand dois-je utiliser l'un ou l'autre? Quelle stratégie est la meilleure en termes d'utilisation des ressources?

Un pool de threads de taille fixe semble être un bon candidat lorsque nous allons limiter le nombre de tâches simultanées à des fins de gestion des ressources .

Par exemple, si nous allons utiliser un exécuteur pour gérer les demandes du serveur Web, un exécuteur fixe peut gérer les rafales de demandes plus raisonnablement.

Pour une gestion encore meilleure des ressources, il est fortement recommandé de créer une configuration personnalisée ThreadPoolExecutoravec une BlockingQueue<T>implémentation limitée couplée à une solution raisonnable RejectedExecutionHandler.


Pool de threads mis en cache


Voici comment ça Executors.newCachedThreadPool()marche:

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

Comme vous pouvez le voir:

  • Le pool de threads peut passer de zéro thread à Integer.MAX_VALUE. En pratique, le pool de threads est illimité.
  • Si un thread est inactif pendant plus d'une minute, il peut se terminer. Ainsi, le pool peut diminuer si les threads restent trop inactifs.
  • Si tous les threads alloués sont occupés pendant qu'une nouvelle tâche arrive, alors cela crée un nouveau thread, car offrir une nouvelle tâche à un SynchronousQueueéchoue toujours quand il n'y a personne à l'autre bout pour l'accepter!

Quand dois-je utiliser l'un ou l'autre? Quelle stratégie est la meilleure en termes d'utilisation des ressources?

Utilisez-le lorsque vous avez beaucoup de tâches prévisibles à court terme.


5

Vous devez utiliser newCachedThreadPool uniquement lorsque vous avez des tâches asynchrones de courte durée comme indiqué dans Javadoc, si vous soumettez des tâches qui prennent plus de temps à traiter, vous finirez par créer trop de threads. Vous pouvez atteindre 100% du processeur si vous soumettez des tâches de longue durée à un rythme plus rapide à newCachedThreadPool ( http://rashcoder.com/be-careful- while-using-executors-newcachedthreadpool/ ).


1

Je fais quelques tests rapides et j'ai les résultats suivants:

1) si vous utilisez SynchronousQueue:

Une fois que les threads atteignent la taille maximale, tout nouveau travail sera rejeté à l'exception comme ci-dessous.

Exception dans le thread "main" java.util.concurrent.RejectedExecutionException: tâche java.util.concurrent.FutureTask@3fee733d rejetée de java.util.concurrent.ThreadPoolExecutor@5acf9800 [En cours d'exécution, taille du pool = 3, threads actifs = 3, tâches en file d'attente = 0, tâches terminées = 0]

à java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)

2) si vous utilisez LinkedBlockingQueue:

Les threads n'augmentent jamais de la taille minimale à la taille maximale, ce qui signifie que le pool de threads est de taille fixe comme taille minimale.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.