Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Apprendre Pool ForkJoin | Meilleures Pratiques de la Programmation Multithread
Multithreading en Java

bookPool ForkJoin

La classe ForkJoinPool en Java, utilisée avec le framework Fork/Join, en est précisément la concrétisation. Elle fournit des mécanismes pour gérer des tâches pouvant être divisées en sous-tâches plus petites et exécutées en parallèle.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool repose sur le concept de deux actions fondamentales : fork et join. Fork correspond à l'action de diviser une tâche importante en plusieurs sous-tâches plus petites pouvant être exécutées en parallèle. Join désigne le processus par lequel les résultats de ces sous-tâches sont combinés afin de former le résultat de la tâche initiale.

Étude de cas

Supposons que vous deviez analyser un ensemble de données volumineux pouvant être divisé en plusieurs sous-ensembles. Si chaque ensemble de données est traité séparément puis que les résultats sont fusionnés, il est possible de d’accélérer considérablement le traitement, en particulier sur les systèmes multiprocesseurs.

Comment utiliser ForkJoinPool

Il existe 2 classes permettant d’implémenter des tâches : RecursiveTask et RecursiveAction. Toutes deux nécessitent l’implémentation de la méthode abstraite compute(). Dans RecursiveTask, la méthode compute() retourne une valeur, tandis que dans RecursiveAction, la méthode compute() retourne void.

Main.java

Main.java

copy
12345678
class ExampleTask extends RecursiveTask<String> { @Override protected String compute() { // code return null; } }

Lorsque nous héritons de RecursiveTask, il est nécessaire de spécifier le type de données que notre méthode compute() va retourner en utilisant la syntaxe RecursiveTask<String>.

Main.java

Main.java

copy
1234567
class ExampleAction extends RecursiveAction { @Override protected void compute() { // code } }

Ici, c'est l'inverse : il n'est pas nécessaire de spécifier explicitement le type dans RecursiveAction, car notre méthode ne retournera rien et se contentera d'exécuter la tâche.

Démarrage d'une tâche

Il est possible de démarrer une tâche pour exécution sans même utiliser ForkJoinPool, simplement en utilisant les méthodes fork() et join().

Il est important de noter que la méthode fork() envoie la tâche à un certain thread. La méthode join() est utilisée pour obtenir le résultat.

Main.java

Main.java

copy
12345
public static void main(String[] args) { ExampleTask simpleClass = new ExampleTask(); simpleClass.fork(); System.out.println(simpleClass.join()); }

Principales méthodes de ForkJoinPool

  • invoke() : Démarre une tâche dans le pool et attend sa complétion. Retourne le résultat de l'exécution de la tâche ;
  • submit() : Soumet une tâche au pool, mais ne bloque pas le thread courant pendant l'attente de la complétion de la tâche. Peut être utilisé pour soumettre des tâches et récupérer leurs objets Future ;
  • execute() : Exécute une tâche dans le pool, mais ne retourne pas de résultat et ne bloque pas le thread courant.

Comment démarrer une tâche avec ForkJoinPool, c'est très simple !

Main.java

Main.java

copy
12345
public static void main(String[] args) { ExampleAction simpleClass = new ExampleAction(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); }

Mais quelle est la différence entre l'exécution avec fork(), join() et via la classe ForkJoinPool ?

C'est très simple ! Lors de l'utilisation de la première méthode avec les méthodes fork(), join(), la tâche démarre dans le même thread où ces méthodes sont appelées, bloquant ce thread, tandis qu'avec la classe ForkJoinPool, un thread est attribué depuis le pool et la tâche s'exécute dans ce thread sans bloquer le thread principal.

Examinons cela de plus près, supposons que nous ayons une telle implémentation :

Main.java

Main.java

copy
12345678
class ExampleRecursiveTask extends RecursiveTask<String> { @Override protected String compute() { System.out.println("Thread: " + Thread.currentThread().getName()); return "Wow, it works!!!"; } }

Et nous souhaitons exécuter ce code en utilisant fork() et join(). Observons ce qui sera affiché dans la console et quel thread effectuera cette tâche.

Main.java

Main.java

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); simpleClass.fork(); System.out.println(simpleClass.join()); } }

Et nous obtenons cette sortie dans la console :

Thread: main
Wow, it works!!!

Examinons maintenant ce qui se passe si nous exécutons avec ForkJoinPool :

Main.java

Main.java

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); } }

Et nous obtenons la conclusion suivante :

Thread: ForkJoinPool-1-worker-1
Wow, it works!!!

Comme vous pouvez le constater, la différence réside dans le fait qu'en utilisant la première méthode, la tâche est exécutée dans le même thread qui a appelé cette tâche (thread principal). Mais si l’on utilise la deuxième méthode, le thread est pris dans le pool de threads ForkJoinPool et le thread principal qui a appelé cette logique n’est pas bloqué et continue son exécution !

Comment implémenter Fork/Join dans le code

La manière la plus simple d'expliquer cela serait à travers une vidéo, plutôt que de vous fournir 50 à 80 lignes de code et de l'expliquer point par point.

Note
Remarque

ForkJoinPool est efficace pour les tâches pouvant être facilement divisées en sous-tâches plus petites. Cependant, si les tâches sont trop petites, l'utilisation de ForkJoinPool peut ne pas apporter de gain de performance significatif.

Tout était clair ?

Comment pouvons-nous l'améliorer ?

Merci pour vos commentaires !

Section 4. Chapitre 2

Demandez à l'IA

expand

Demandez à l'IA

ChatGPT

Posez n'importe quelle question ou essayez l'une des questions suggérées pour commencer notre discussion

Suggested prompts:

Can you explain the difference between RecursiveTask and RecursiveAction again?

How do I decide what threshold to use when splitting tasks?

Can you give a simple code example of using ForkJoinPool?

Awesome!

Completion rate improved to 3.33

bookPool ForkJoin

Glissez pour afficher le menu

La classe ForkJoinPool en Java, utilisée avec le framework Fork/Join, en est précisément la concrétisation. Elle fournit des mécanismes pour gérer des tâches pouvant être divisées en sous-tâches plus petites et exécutées en parallèle.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool repose sur le concept de deux actions fondamentales : fork et join. Fork correspond à l'action de diviser une tâche importante en plusieurs sous-tâches plus petites pouvant être exécutées en parallèle. Join désigne le processus par lequel les résultats de ces sous-tâches sont combinés afin de former le résultat de la tâche initiale.

Étude de cas

Supposons que vous deviez analyser un ensemble de données volumineux pouvant être divisé en plusieurs sous-ensembles. Si chaque ensemble de données est traité séparément puis que les résultats sont fusionnés, il est possible de d’accélérer considérablement le traitement, en particulier sur les systèmes multiprocesseurs.

Comment utiliser ForkJoinPool

Il existe 2 classes permettant d’implémenter des tâches : RecursiveTask et RecursiveAction. Toutes deux nécessitent l’implémentation de la méthode abstraite compute(). Dans RecursiveTask, la méthode compute() retourne une valeur, tandis que dans RecursiveAction, la méthode compute() retourne void.

Main.java

Main.java

copy
12345678
class ExampleTask extends RecursiveTask<String> { @Override protected String compute() { // code return null; } }

Lorsque nous héritons de RecursiveTask, il est nécessaire de spécifier le type de données que notre méthode compute() va retourner en utilisant la syntaxe RecursiveTask<String>.

Main.java

Main.java

copy
1234567
class ExampleAction extends RecursiveAction { @Override protected void compute() { // code } }

Ici, c'est l'inverse : il n'est pas nécessaire de spécifier explicitement le type dans RecursiveAction, car notre méthode ne retournera rien et se contentera d'exécuter la tâche.

Démarrage d'une tâche

Il est possible de démarrer une tâche pour exécution sans même utiliser ForkJoinPool, simplement en utilisant les méthodes fork() et join().

Il est important de noter que la méthode fork() envoie la tâche à un certain thread. La méthode join() est utilisée pour obtenir le résultat.

Main.java

Main.java

copy
12345
public static void main(String[] args) { ExampleTask simpleClass = new ExampleTask(); simpleClass.fork(); System.out.println(simpleClass.join()); }

Principales méthodes de ForkJoinPool

  • invoke() : Démarre une tâche dans le pool et attend sa complétion. Retourne le résultat de l'exécution de la tâche ;
  • submit() : Soumet une tâche au pool, mais ne bloque pas le thread courant pendant l'attente de la complétion de la tâche. Peut être utilisé pour soumettre des tâches et récupérer leurs objets Future ;
  • execute() : Exécute une tâche dans le pool, mais ne retourne pas de résultat et ne bloque pas le thread courant.

Comment démarrer une tâche avec ForkJoinPool, c'est très simple !

Main.java

Main.java

copy
12345
public static void main(String[] args) { ExampleAction simpleClass = new ExampleAction(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); }

Mais quelle est la différence entre l'exécution avec fork(), join() et via la classe ForkJoinPool ?

C'est très simple ! Lors de l'utilisation de la première méthode avec les méthodes fork(), join(), la tâche démarre dans le même thread où ces méthodes sont appelées, bloquant ce thread, tandis qu'avec la classe ForkJoinPool, un thread est attribué depuis le pool et la tâche s'exécute dans ce thread sans bloquer le thread principal.

Examinons cela de plus près, supposons que nous ayons une telle implémentation :

Main.java

Main.java

copy
12345678
class ExampleRecursiveTask extends RecursiveTask<String> { @Override protected String compute() { System.out.println("Thread: " + Thread.currentThread().getName()); return "Wow, it works!!!"; } }

Et nous souhaitons exécuter ce code en utilisant fork() et join(). Observons ce qui sera affiché dans la console et quel thread effectuera cette tâche.

Main.java

Main.java

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); simpleClass.fork(); System.out.println(simpleClass.join()); } }

Et nous obtenons cette sortie dans la console :

Thread: main
Wow, it works!!!

Examinons maintenant ce qui se passe si nous exécutons avec ForkJoinPool :

Main.java

Main.java

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); } }

Et nous obtenons la conclusion suivante :

Thread: ForkJoinPool-1-worker-1
Wow, it works!!!

Comme vous pouvez le constater, la différence réside dans le fait qu'en utilisant la première méthode, la tâche est exécutée dans le même thread qui a appelé cette tâche (thread principal). Mais si l’on utilise la deuxième méthode, le thread est pris dans le pool de threads ForkJoinPool et le thread principal qui a appelé cette logique n’est pas bloqué et continue son exécution !

Comment implémenter Fork/Join dans le code

La manière la plus simple d'expliquer cela serait à travers une vidéo, plutôt que de vous fournir 50 à 80 lignes de code et de l'expliquer point par point.

Note
Remarque

ForkJoinPool est efficace pour les tâches pouvant être facilement divisées en sous-tâches plus petites. Cependant, si les tâches sont trop petites, l'utilisation de ForkJoinPool peut ne pas apporter de gain de performance significatif.

Tout était clair ?

Comment pouvons-nous l'améliorer ?

Merci pour vos commentaires !

Section 4. Chapitre 2
some-alt