Pool 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
1ForkJoinPool 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
12345678class 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
1234567class 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
12345public 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 objetsFuture;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
12345public 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
12345678class 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
1234567public 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
1234567public 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.
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.
Merci pour vos commentaires !
Demandez à l'IA
Demandez à l'IA
Posez n'importe quelle question ou essayez l'une des questions suggérées pour commencer notre discussion
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
Pool 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
1ForkJoinPool 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
12345678class 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
1234567class 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
12345public 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 objetsFuture;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
12345public 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
12345678class 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
1234567public 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
1234567public 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.
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.
Merci pour vos commentaires !