Executors.newCachedThreadPool () versus Executors.newFixedThreadPool ()

newCachedThreadPool() versus newFixedThreadPool()

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

Je pense que les documents expliquent 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 exécutant une queue sans limites partagée. À tout moment, au plus les threads nThreads seront des tâches de traitement actives. Si des tâches supplémentaires sont soumises lorsque tous les threads sont actifs, ils attendent dans la queue jusqu’à ce qu’un thread soit disponible. Si un thread se termine en raison d’une défaillance au cours de l’exécution avant la fermeture, un nouveau thread 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 en fonction des 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 terminés et supprimés du cache. Ainsi, un pool qui rest inactif assez longtemps ne consumra aucune ressource. Notez que des pools avec des propriétés similaires mais des détails différents (par exemple, des parameters de délai d’attente) peuvent être créés à l’aide des constructeurs ThreadPoolExecutor.

En termes de ressources, le newFixedThreadPool gardera tous les threads en cours d’exécution jusqu’à ce qu’ils soient explicitement terminés. Dans les newCachedThreadPool threads qui n’ont pas été utilisés pendant soixante secondes sont terminé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 longues, je vous suggérerais le FixedThreadPool . En ce qui concerne CachedThreadPool , les documents indiquent que “ces pools vont généralement améliorer les performances des programmes qui exécutent de nombreuses tâches asynchrones de courte durée”.

Juste pour compléter les autres réponses, je voudrais citer Efficace Java, 2e édition, par Joshua Bloch, chapitre 10, article 68:

“Choisir le service exécutant pour une application particulière peut être difficile. 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” 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 queue mais transférées immédiatement à 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, davantage de threads seront créés, ce qui ne fera qu’empirer les choses.

Par conséquent, dans un serveur de production fortement 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.

Si vous voyez le code dans le grepcode, vous verrez qu’ils appellent ThreadPoolExecutor. en interne et en définissant leurs propriétés. Vous pouvez en créer un pour mieux contrôler vos besoins.

 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); } 

Executors.newCachedThreadPool() n’est pas un bon choix pour le code de serveur qui gère plusieurs clients et demandes simultanées.

Pourquoi? Il y a essentiellement deux problèmes (connexes) avec:

  1. C’est illimité, ce qui signifie que vous ouvrez la porte à quiconque paralyse votre JVM en injectant simplement plus de travail dans le service (attaque DoS). Les threads consumnt une quantité de mémoire non négligeable 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 d’autres disjoncteurs sont en place).

  2. Le problème sans limite est exacerbé par le fait que l’Executor est dirigé par une SynchronousQueue ce 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 du serveur. Lorsque le processeur est saturé, les tâches existantes sont plus longues à terminer. Pourtant, de plus en plus de tâches sont soumises et plus de threads sont créés. Les tâches sont donc plus longues et plus longues à réaliser. Lorsque le processeur est saturé, plus de threads ne sont certainement pas ce dont le serveur a besoin.

Voici mes recommandations:

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

Si vous ne vous souciez pas de la queue illimitée des tâches Callable / Runnable , vous pouvez en utiliser une. Comme suggéré par bruno, je préfère aussi newFixedThreadPool à newCachedThreadPool entre ces deux.

Mais ThreadPoolExecutor offre des fonctionnalités plus flexibles par rapport à newFixedThreadPool ou newCachedThreadPool

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

Avantages:

  1. Vous avez un contrôle total sur la taille de BlockingQueue . Ce n’est pas sans limites, contrairement aux deux options précédentes. Je ne vais pas me sortir d’une erreur de mémoire en raison d’une accumulation importante de tâches Callable / Runnable en cours dans une turbulence inattendue du système.

  2. Vous pouvez implémenter une stratégie de traitement de rejet personnalisée OU utiliser l’une des stratégies:

    1. Dans le ThreadPoolExecutor.AbortPolicy par défaut, le gestionnaire renvoie une exception RejectedExecutionException à l’exécution lors du rejet.

    2. Dans ThreadPoolExecutor.CallerRunsPolicy , le thread qui appelle exécute lui-même exécute la tâche. Cela fournit un mécanisme de contrôle de rétroaction simple qui ralentira le taux de soumission de nouvelles tâches.

    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 queue de travail est supprimée, puis l’exécution est exécutée à nouveau (ce qui peut échouer à nouveau et entraîner sa répétition).

  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 le statut du démon de thread
    3. Pour définir la priorité des threads

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 bash 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/ ).

Je fais des 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é avec l’exception comme ci-dessous.

Exception dans le thread “main” java.util.concurrent.RejectedExecutionException: Tâche java.util.concurrent.FutureTask@3fee733d rejetée par java.util.concurrent.ThreadPoolExecutor@5acf9800 [En cours d’exécution, taille du pool = 3, threads actifs = 3, tâches en queue = 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.