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 docs expliquent assez bien la différence et l’utilisation de ces deux fonctions:
Crée un pool de threads qui réutilise un fichier nombre fixe de threads opérant off une file d'attente partagée sans limite. À n'importe point, au plus nThreads threads sera être des tâches de traitement actives. Si des tâches supplémentaires sont soumises lorsque tous les threads sont actifs, ils vont attendre dans la file d'attente jusqu'à ce qu'un thread soit disponible. Si un fil se termine due à un échec lors de l'exécution avant l’arrêt, un nouveau portera sa place si nécessaire pour exécuter tâches suivantes. Les discussions dans le pool existera jusqu’à ce qu’il soit explicitement fermer.
Crée un pool de threads qui crée un nouveau fichier si nécessaire, mais les réutilisera les threads précédemment construits quand. Ils sont disponibles. Ces piscines seront généralement améliorer les performances de programmes qui exécutent beaucoup de courte durée tâches asynchrones. Appels à exécuter réutilisera précédemment construit fils si disponibles. Si aucun existant le fil est disponible, un nouveau fil sera être créé et ajouté à la piscine . Fils qui n'ont pas été utilisés pour soixante secondes sont terminées et retiré de la cache. Ainsi, une piscine qui reste inactif assez longtemps ne pas consommer de ressources. Notez que piscines avec des propriétés similaires, mais différents détails (par exemple, les paramètres de délai d'expiration .__) peuvent être créés utiliser les constructeurs ThreadPoolExecutor.
En termes de ressources, la variable newFixedThreadPool
maintiendra tous les threads en cours d'exécution jusqu'à ce qu'ils soient explicitement terminés. Dans la newCachedThreadPool
, les threads qui n'ont pas été utilisés pendant soixante secondes sont terminés et supprimés du cache.
Dans ces conditions, 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 la FixedThreadPool
. En ce qui concerne la CachedThreadPool
, la documentation indique que "ces pools amélioreront généralement les performances des programmes exécutant de nombreuses tâches asynchrones de courte durée".
Pour compléter les autres réponses, je voudrais citer Effective Java, 2e édition, par Joshua Bloch, chapitre 10, point 68:
"Choisir le 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 “fait ce qu'il faut”. Mais un pool de threads mis en cache n'est pas un bon choix pour un serveur de production très chargé!
Dans un pool de threads mis en cache, les tâches soumises ne sont pas mises en file d'attente, mais immédiatement transférées à un thread pour exécution. Si aucun fil 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'aggraver 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 maximum contrôle. "
Si vous voyez le code dans le grepcode, vous verrez qu'ils appellent ThreadPoolExecutor. en interne et définissant leurs propriétés. Vous pouvez créer le vôtre pour avoir un meilleur contrôle 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>());
}
C’est vrai, 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:
C'est illimité, ce qui signifie que vous ouvrez la porte à une invalidation de votre JVM en injectant simplement plus de travail dans le service (attaque par déni de service). Les threads consomment 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 très facile de renverser un serveur de cette façon (à moins que d'autres disjoncteurs ne soient en place).
Le problème sans bornes est exacerbé par le fait que l'exécutant est précédé de 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 serveur. Lorsque le processeur est saturé, les tâches existantes prennent plus de temps. Cependant, davantage de tâches sont en cours de soumission et plus de threads sont créés. Les tâches prennent donc de plus en plus de temps. Lorsque le processeur est saturé, davantage 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 un ThreadPoolExecutor. avec un nombre maximum de threads défini;
Si vous n'êtes pas inquiet à propos de la file d'attente sans limite 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 fournit des fonctionnalités plus flexibles que newFixedThreadPool
ou newCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Avantages:
Vous avez le contrôle total sur la taille BlockingQueue. Ce n'est pas illimité contrairement aux options précédentes. Je ne parviendrai pas à éviter les erreurs de mémoire en raison de l'énorme accumulation de tâches Callable/Runnable en attente dans des turbulences inattendues dans le système.
Vous pouvez implémenter une stratégie personnalisée de rejet OR en utilisant l'une des stratégies suivantes:
Dans le ThreadPoolExecutor.AbortPolicy
par défaut, le gestionnaire lève une exception d'exécution RejectedExecutionException lors du rejet.
Dans ThreadPoolExecutor.CallerRunsPolicy
, le thread qui appelle s'exécute lui-même exécute la tâche. Cela fournit un mécanisme simple de contrôle des commentaires qui ralentira la vitesse à laquelle les nouvelles tâches sont soumises.
Dans ThreadPoolExecutor.DiscardPolicy
, une tâche qui ne peut pas être exécutée est simplement supprimée.
Dans ThreadPoolExecutor.DiscardOldestPolicy
, si l'exécuteur n'est pas arrêté, la tâche située en tête de la file d'attente de travail est abandonnée, puis son exécution est tentée à nouveau (ce qui peut échouer à nouveau et entraîner la répétition de cette opération).
Vous pouvez implémenter une fabrique de threads personnalisée pour les cas d'utilisation ci-dessous:
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 dont le traitement prend plus de temps, vous créez trop de threads. Vous pouvez atteindre 100% de votre CPU si vous soumettez des tâches longues à un rythme plus rapide à newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ).
Je fais quelques tests rapides et j'ai les résultats suivants:
1) si vous utilisez SynchronousQueue:
Une fois que les threads ont atteint la taille maximale, tout nouveau travail sera rejeté à l'exception de ce qui suit.
Exception dans le fil "principal" Java.util.concurrent.RejectedExecutionException: tâche Java.util.concurrent.FutureTask@3fee733d rejetée de Java.util.concurrent.ThreadPoolExecutor@5acf9800 [Tâches en cours, taille du pool = 3, threads actifs = 3, en attente = 0, tâches terminées = 0]
sur 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 a une taille fixe comme taille minimale.