web-dev-qa-db-fra.com

Java Forkjoin vs ExecutorService - quand utiliser lequel?

Je viens de terminer la lecture de ce post: Quel est l'avantage d'un ThreadPoolExecutor Java-5 sur un ForkJoinPool Java-7? et j'ai estimé que la réponse n'était pas assez directe.

Pouvez-vous expliquer dans un langage simple et des exemples, quels sont les compromis entre Java 7 framework Fork-Join et les anciennes solutions?

J'ai également lu le hit n ° 1 de Google sur le sujet Astuce Java: Quand utiliser ForkJoinPool vs ExecutorService de javaworld.com mais l'article ne répond pas à la question du titre quand , il parle surtout des différences d'api ...

46
Parobay

Fork-join vous permet d'exécuter facilement des tâches de division et de conquête, qui doivent être implémentées manuellement si vous souhaitez l'exécuter dans ExecutorService. En pratique, ExecutorService est généralement utilisé pour traiter simultanément de nombreuses demandes indépendantes (aka transaction) et fork-join lorsque vous souhaitez accélérer une tâche cohérente.

44
Jakub Kubrynski

La jointure en fourche est particulièrement bonne pour les problèmes récursifs , où une tâche implique l'exécution de sous-tâches, puis le traitement de leurs résultats. (Ceci est généralement appelé "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 le threading conventionnel (par exemple via un ExecutorService), vous vous retrouvez avec des threads bloqués en attendant que d'autres threads leur fournissent des résultats.

D'un autre côté, si le problème n'a pas ces caractéristiques, il n'y a pas de réel avantage à utiliser fork-join.


Voici un article "Java Tips" qui va plus en détail:

31
Stephen C

Java 8 fournit une API supplémentaire dans les exécuteurs

static ExecutorService  newWorkStealingPool()

Crée un pool de threads de vol de travail en utilisant tous les processeurs disponibles comme niveau de parallélisme cible.

Avec l'ajout de cette API, Executors fournit différents types d'options ExecutorService .

Selon vos besoins, vous pouvez en choisir un ou rechercher ThreadPoolExecutor qui offre un meilleur contrôle sur la taille de la file d'attente des tâches délimitées, les mécanismes RejectedExecutionHandler.

  1. 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 illimitée partagée.

  2. static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

    Crée un pool de threads qui peut planifier des commandes à exécuter après un délai donné ou à exécuter périodiquement.

  3. static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

    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, et utilise ThreadFactory fourni pour créer de nouveaux threads si nécessaire.

  4. static ExecutorService newWorkStealingPool(int parallelism)

    Crée un pool de threads qui conserve suffisamment de threads pour prendre en charge le niveau de parallélisme donné et peut utiliser plusieurs files d'attente pour réduire les conflits.

Chacune de ces API est ciblée pour répondre aux besoins commerciaux respectifs de votre application. Lequel utiliser dépendra de vos exigences de cas d'utilisation.

par exemple.

  1. Si vous souhaitez traiter toutes les tâches soumises par ordre d'arrivée, utilisez simplement newFixedThreadPool(1)

  2. Si vous souhaitez optimiser les performances de gros calcul de tâches récursives, utilisez ForkJoinPool ou newWorkStealingPool

  3. Si vous souhaitez exécuter certaines tâches périodiquement ou à un certain moment dans le futur, utilisez newScheduledThreadPool

Jetez un oeil à un autre Nice article par PeterLawrey sur ExecutorService cas d'utilisation.

Question SE associée:

Java Fork/Join pool, ExecutorService et CountDownLatch

13
Ravindra babu

Brian Goetz décrit le mieux la situation: https://www.ibm.com/developerworks/library/j-jtp11137/index.html

L'utilisation de pools de threads conventionnels pour implémenter la jointure en fourche est également difficile car les tâches de jointure en fourche passent une grande partie de leur vie à attendre d'autres tâches. Ce comportement est une recette pour l'impasse de la famine des threads, sauf si les paramètres sont soigneusement choisis pour limiter le nombre de tâches créées ou si le pool lui-même n'est pas limité. Les pools de threads conventionnels sont conçus pour des tâches qui sont indépendantes les unes des autres et sont également conçus avec des tâches à gros grains potentiellement bloquantes à l'esprit - les solutions de jointure en fourche ne produisent ni l'un ni l'autre.

Je recommande de lire l'intégralité du message, car il présente un bon exemple de la raison pour laquelle vous souhaitez utiliser un pool de jointure en fourche. Il a été écrit avant que ForkJoinPool ne devienne officiel, donc la méthode coInvoke() à laquelle il se réfère est devenue invokeAll().

5
bhh1988

Le framework Fork-Join est une extension du framework Executor pour résoudre en particulier les problèmes "d'attente" dans les programmes récursifs multithread. En fait, les nouvelles classes de framework Fork-Join s'étendent toutes à partir des classes existantes du framework Executor.

Le framework Fork-Join comporte 2 caractéristiques

  • Vol de travail (un thread inactif vole le travail d'un thread dont les tâches sont en file d'attente plus qu'il ne peut traiter actuellement)
  • Capacité à décomposer récursivement les tâches et à collecter les résultats. (Apparemment, cette exigence doit avoir surgi avec la conception de la notion de traitement parallèle ... mais il manquait un cadre d'implémentation solide dans Java till Java 7)

Si les besoins de traitement parallèle sont strictement récursifs, il n'y a pas d'autre choix que d'opter pour Fork-Join, sinon l'exécuteur ou le framework Fork-Join devrait le faire, bien que Fork-Join puisse être utilisé pour mieux utiliser les ressources en raison des threads inactifs "voler" certaines tâches de threads plus occupés.

4
Murali Mohan

Fork Join est une implémentation d'ExecuterService. La principale différence est que cette implémentation crée un pool de travail DEQUE. Où la tâche est insérée d'un côté mais retirée de n'importe quel côté. Cela signifie que si vous avez créé new ForkJoinPool() il recherchera le CPU disponible et créera autant de threads de travail. Il répartit ensuite la charge uniformément sur chaque thread. Mais si un thread fonctionne lentement et d'autres sont rapides, ils choisiront la tâche à partir du thread 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

Étape 2:
W1 -> 5,4
W2 -> 10,9,8,7,

Étape 3:
W1 -> 10,5,4
W2 -> 9,8,7,

Alors que le service Executor crée le nombre demandé de threads et applique une file d'attente de blocage pour stocker toutes les tâches en attente restantes. Si vous avez utilisé cachedExecuterService, cela créera un seul thread pour chaque travail et il n'y aura pas de file d'attente.

4
krmanish007