Quel est l'avantage D'un ThreadPoolExecutor Java-5 sur un Forkjoinpool Java-7?
Java 5 a introduit la prise en charge de l'exécution de tâches asynchrones par un pool de threads sous la forme du framework Executor, dont le cœur est le pool de threads implémenté par java.util.simultané.ThreadPoolExecutor. Java 7 a ajouté un pool de threads alternatif sous la forme de java.util.simultané.ForkJoinPool.
En regardant leur API respective, ForkJoinPool fournit un sur-Ensemble de la fonctionnalité de ThreadPoolExecutor dans les scénarios standard (bien que ThreadPoolExecutor offre à proprement parler plus de possibilités de réglage que ForkJoinPool). Ajoutant à cela l'observation que les tâches fork/join semblent être plus rapides (peut-être en raison du Planificateur de vol de travail), ont besoin de moins de threads (en raison de l'opération de jointure non bloquante), on pourrait avoir l'impression que ThreadPoolExecutor a été remplacé par ForkJoinPool.
Mais est-ce vraiment correct? Tout le matériel que j'ai lu semble résumer à une distinction plutôt vague entre les deux types de pools de threads:
- ForkJoinPool est pour de nombreuses tâches dépendantes, générées par des tâches, courtes, presque jamais bloquantes (c'est-à-dire intensives en calcul)
- ThreadPoolExecutor est pour quelques tâches indépendantes, générées en externe, longues, parfois bloquantes
Cette distinction Est-elle correcte? Pouvons-nous dire quelque chose de plus précis à ce sujet?
4 réponses
ThreadPool (TP) et ForkJoinPool (FJ) sont ciblés vers différents cas d'utilisation. La principale différence réside dans le nombre de files d'attente employées par les différents exécuteurs qui décident quel type de problèmes sont mieux adaptés à l'un ou l'autre exécuteur.
L'exécuteur FJ a n (aka niveau de parallélisme) Files d'attente simultanées séparées (deques) tandis que L'exécuteur TP n'a qu'une seule file d'attente concurrente (ces Files d'attente/deques peuvent être des implémentations personnalisées ne Suivant pas L'API JDK Collections). En conséquence, dans scénarios où vous avez un grand nombre de tâches (généralement relativement courtes) générées, L'exécuteur FJ fonctionnera mieux car les files d'attente indépendantes minimiseront les opérations simultanées et les vols peu fréquents aideront à l'équilibrage de charge. Dans TP en raison de la file d'attente unique, il y aura des opérations simultanées chaque fois que le travail est désactivé et il agira comme un goulot d'étranglement relatif et limitera les performances.
En revanche, s'il y a relativement moins de tâches de longue durée, la file d'attente unique dans TP n'est plus un goulot d'étranglement pour les performances. Cependant, les files d'attente n-indépendantes et les tentatives de vol de travail relativement fréquentes deviendront maintenant un goulot d'étranglement dans FJ car il peut y avoir de nombreuses tentatives futiles pour voler du travail qui ajoutent aux frais généraux.
De plus, l'algorithme de vol de travail dans FJ suppose que les tâches (plus anciennes) volées de la deque produiront suffisamment de tâches parallèles pour réduire le nombre de vols. Par exemple, dans quicksort ou mergesort où les tâches plus anciennes équivalent à des tableaux plus grands, ceux-ci les tâches généreront plus de tâches et garderont la file d'attente non vide et réduiront le nombre de vols globaux. Si ce n'est pas le cas dans une application donnée, les tentatives de vol fréquentes deviennent à nouveau un goulot d'étranglement. Ceci est également noté dans le javadoc pour ForkJoinPool :
Cette classe fournit des méthodes de vérification d'état (par exemple getStealCount()) qui sont destinés à aider à développer, tuning, et le suivi applications fork / join.
Lecture recommandée http://gee.cs.oswego.edu/dl/jsr166/dist/docs/ De la documentation pour ForkJoinPool:
Un ForkJoinPool diffère des autres types D'ExecutorService principalement par vertu d'employer le vol de travail: tous les threads de la piscine tentent de rechercher et exécuter des tâches soumises au pool et / ou créées par d'autres tâches actives (éventuellement bloquer l'attente de travail si aucune n'existe). Cela permet un traitement efficace lorsque la plupart des tâches génèrent d'autres sous tâches (que faire la plupart des ForkJoinTasks), ainsi que lorsque de nombreuses petites tâches sont soumis au pool par des clients externes. Surtout lors du réglage asyncMode à true dans les constructeurs, ForkJoinPools peut également être approprié pour une utilisation avec des tâches de style événement qui ne sont jamais jointes.
Le cadre de jointure fork est utile pour l'exécution parallèle tandis que le service executor permet une exécution simultanée et il y a une différence. Voir ce et ce.
Le cadre de jointure fork aussi permet le vol de travail (utilisation d'une Deque).
Cet article est une bonne lecture.
AFAIK, ForkJoinPool
fonctionne mieux si vous un gros morceau de travail et vous voulez qu'il soit brisé automatiquement. ThreadPoolExecutor
est un meilleur choix si vous savez comment vous voulez que le travail rompu. Pour cette raison, j'ai tendance à utiliser ce dernier parce que j'ai déterminé comment je veux que le travail soit brisé. En tant que tel ce n'est pas pour tous.
Ça ne vaut rien que quand il s'agit de morceaux de logique métier relativement Aléatoires, un ThreadPoolExecutor fera tout ce dont vous avez besoin, alors pourquoi le rendre plus compliqué que nécessaire.
Comparons les différences dans les constructeurs:
ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
ForkJoinPool
ForkJoinPool(int parallelism,
ForkJoinPool.ForkJoinWorkerThreadFactory factory,
Thread.UncaughtExceptionHandler handler,
boolean asyncMode)
Le seul avantage que j'ai vu dans ForkJoinPool
: mécanisme de vol de travail par des threads inactifs.
Java 8 a introduit une API supplémentaire dans Executors-newWorkStealingPool pour créer un pool de vol de travail. Vous n'avez pas besoin de créer RecursiveTask
et RecursiveAction
mais vous pouvez toujours utiliser ForkJoinPool
.
public static ExecutorService newWorkStealingPool()
Crée un pool de threads Volant de travail utilisant tous les processeurs disponibles comme niveau de parallélisme cible.
Avantages de ThreadPoolExecutor sur ForkJoinPool:
- Vous pouvez contrôler la taille de la file d'attente des tâches dans
ThreadPoolExecutor
contrairement àForkJoinPool
. - Vous pouvez appliquer la Politique de rejet lorsque vous avez manqué de votre capacité contrairement à
ForkJoinPool
J'aime ces deux caractéristiques dans ThreadPoolExecutor
, qui maintient la santé du système en bon état.
Modifier:
Jetez un oeil à cet article pour les cas d'utilisation de différents types de pools de threads de service Executor et l'évaluation des fonctionnalités Forkjoin Pool.