Java Fork/Join vs ExecutorService-quand utiliser lequel?
je viens de terminer la lecture de ce post: Quel est l'avantage D'un Java-5 ThreadPoolExecutor sur un Java-7 ForkJoinPool? , et pense que la réponse n'est pas assez droite.
pouvez-vous expliquer dans un langage simple et des exemples, quels sont les" compromis entre le cadre Fork-Join de Java 7 et les anciennes solutions?
j'ai aussi lu le hit #1 de Google sur le thème Java Tip: quand à utiliser ForkJoinPool vs ExecutorService de javaworld.com mais l'article ne répond pas à la question de titre quand , il parle surtout des différences api ...
6 réponses
Fork-join vous permet d'exécuter facilement divide and conquer jobs, qui doivent être implémentés manuellement si vous voulez l'exécuter dans ExecutorService. En pratique, ExecutorService est habituellement utilisé pour traiter de nombreuses requêtes indépendantes (transaction alias) simultanément, et fork-join quand vous voulez accélérer une tâche cohérente.
Fork-join est particulièrement bon pour recursive problèmes, où une tâche implique l'exécution de sous-tâches et le traitement de leurs résultats. (Cela s'appelle typiquement "diviser pour mieux régner"... mais cela ne révèle pas les caractéristiques essentielles.)
si vous essayez de résoudre un problème récursif comme celui-ci en utilisant un filetage conventionnel (par exemple via un ExecutorService) vous finissez avec des fils attachés en attendant que d'autres fils leur donnent des résultats.
d'autre part, si le problème n'a pas ces caractéristiques, il n'y a pas d'avantage réel de l'utilisation de fourche-jointure.
Java 8 fournit une API de plus dans les exécuteurs
static ExecutorService newWorkStealingPool()
crée un pool de threads en utilisant tous les processeurs disponibles comme niveau de parallélisme cible.
avec l'ajout de cette API, Executors fournit différents types de ExecutorService options.
selon vos besoins, vous pouvez choisir L'un D'eux ou vous pouvez rechercher ThreadPoolExecutor qui fournit un meilleur contrôle sur la taille de la file D'attente bornée des tâches, RejectedExecutionHandler
mécanismes.
-
static ExecutorService newFixedThreadPool(int nThreads)
crée un pool de threads qui réutilise un nombre fixe de threads opérant à partir d'une file d'attente partagée sans limite.
-
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
crée un pool de threads qui peut programmer des commandes pour exécuter après un délai donné, ou pour exécuter périodiquement.
-
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
crée un pool de threads qui crée de nouveaux threads au besoin, mais réutilisera les threads déjà construits quand ils sont disponible, et utilise le ThreadFactory fourni pour créer de nouveaux threads si nécessaire.
-
static ExecutorService newWorkStealingPool(int parallelism)
crée un pool de threads qui maintient suffisamment de threads pour supporter le niveau de parallélisme donné, et peut utiliser plusieurs files d'attente pour réduire la prétention.
chacun de ces Les API sont ciblées pour répondre aux besoins opérationnels respectifs de votre application. À utiliser dépend de votre cas d'utilisation d'exigence.
p.ex.
-
Si vous voulez traiter toutes les tâches dans l'ordre d'arrivée, il suffit d'utiliser
newFixedThreadPool(1)
-
si vous voulez optimiser l'exécution de grands calculs de tâches récursives, utilisez
ForkJoinPool
ounewWorkStealingPool
-
si vous souhaitez exécuter certaines tâches périodiquement ou à un moment donné à l'avenir, utilisez
newScheduledThreadPool
Avoir un coup d'oeil à l'une des plus belles article par PeterLawrey
sur ExecutorService
cas d'utilisation.
Related SE question:
Fork-Join framework est une extension de Executor framework pour traiter particulièrement les questions d'attente dans les programmes multi-threads récursifs. En fait, les nouvelles classes Fork-Join framework s'étendent toutes des classes existantes de L'exécuteur framework.
Il y a 2 caractéristiques de la centrale de Fork-Join cadre
- vol de travail (un fil inactif vole le travail d'un fil ayant des tâches en file d'attente plus qu'il ne peut traiter actuellement)
- capacité de décomposer de façon récursive les tâches et de recueillir les résultats. (Apparemment, cette exigence a dû apparaître avec le conception de la notion de traitement parallèle... mais il manquait un solide cadre de mise en œuvre en Java jusqu'à Java 7)
si les besoins de traitement parallèle sont strictement récursifs, il n'y a pas d'autre choix que D'aller pour Fork-Join, sinon l'exécuteur ou Fork-Join framework devrait faire, bien que bifurcation-Join peut être dit de mieux utiliser les ressources en raison des threads inactifs "voler" certaines tâches de threads plus occupés.
Fork Join est une implémentation de Executervice. La principale différence est que cette implémentation crée deque worker pool. Où la tâche est insérée de l'oneside mais retirée de n'importe quel côté. Cela signifie que si vous avez créé new ForkJoinPool()
il va chercher le CPU disponible et créer que beaucoup de thread de travail. Il répartit ensuite la charge uniformément sur chaque fil. Mais si un fil fonctionne lentement et d'autres sont rapides, ils choisiront la tâche à partir du fil lent. de l'arrière. Les étapes ci-dessous illustreront mieux le vol.
Étape 1 (initialement):
W1 - > 5,4,3,2,1
W2 - > 10,9,8,7,6,6
Étape 2:
W1 - > 5,4
W2 - > 10,9,8,7,
Étape 3:
W1 - > 10,5,4
W2 - > 9,8,7,
alors que le service D'exécuteur testamentaire crée un nombre demandé de fil, et d'appliquer un blocage de la file d'attente pour stocker tout le reste de l'attente de la tâche. Si vous avez utilisé cachedexecutervice, il créera un thread unique pour chaque tâche et il n'y aura pas de file d'attente.
Brian Goetz décrit le mieux la situation: https://www.ibm.com/developerworks/library/j-jtp11137/index.html
L'utilisation des pools de threads conventionnels pour mettre en œuvre la bifurcation-jointure est également un défi parce que les tâches de bifurcation-jointure passent une grande partie de leur vie à attendre d'autres tâches. Ce comportement est une recette pour l'Impasse de la famine de thread, à moins que les paramètres soient soigneusement choisis pour lier le nombre de tâches créées ou le pool lui-même est sans bornes. Les pools de filetage conventionnels sont conçus pour des tâches indépendantes les unes des autres et sont également conçus avec des tâches potentiellement bloquantes et à grain grossier dans les solutions mind-fork - join.
je recommande la lecture de l'ensemble du message, car il a un bon exemple de la raison pour laquelle vous voulez utiliser une piscine fourche-join. Il a été écrit avant ForkJoinPool est devenu officiel, de sorte que le coInvoke()
méthode dont il parle est devenu invokeAll()
.