Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Impara ForkJoinPool | Migliori Pratiche per il Multithreading
Quizzes & Challenges
Quizzes
Challenges
/
Multithreading in Java

bookForkJoinPool

La classe ForkJoinPool in Java per lavorare con il framework Fork/Join rappresenta proprio questa realizzazione. Fornisce meccanismi per la gestione di attività che possono essere suddivise in sotto-attività più piccole ed eseguite in parallelo.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool si basa sul concetto di due azioni fondamentali: fork e join. Fork è un'azione in cui un'attività complessa viene suddivisa in sotto-attività più piccole che possono essere eseguite in parallelo. Join è il processo tramite il quale i risultati di queste sotto-attività vengono combinati per formare il risultato dell'attività originale.

Caso di studio

Immagina di dover analizzare un enorme insieme di dati che può essere suddiviso in diversi sottoinsiemi. Se elabori ciascun insieme di dati separatamente e poi unisci i risultati, puoi velocizzare notevolmente l'elaborazione, soprattutto su sistemi multiprocessore.

Come utilizzare ForkJoinPool

Esistono 2 classi per implementare task al loro interno: RecursiveTask e RecursiveAction. Entrambe richiedono l'implementazione del metodo astratto compute(). In RecursiveTask il metodo compute() restituisce un valore, mentre in RecursiveAction il metodo compute() restituisce void.

Main.java

Main.java

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

Quando ereditiamo da RecursiveTask dobbiamo assicurarci di specificare quale tipo di dato il nostro metodo compute() restituirà utilizzando questa sintassi RecursiveTask<String>.

Main.java

Main.java

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

Qui è il contrario, non è necessario specificare esplicitamente il tipo in RecursiveAction, perché il nostro metodo non restituirà nulla e si limiterà a eseguire il compito.

Avvio di un Task

È possibile avviare un task per l'esecuzione anche senza utilizzare ForkJoinPool, semplicemente usando i metodi fork() e join().

È importante notare che il metodo fork() invia il task a un thread. Il metodo join() viene utilizzato per ottenere il risultato.

Main.java

Main.java

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

Metodi Principali di ForkJoinPool

  • invoke(): Avvia un task nel pool e attende il suo completamento. Restituisce il risultato al termine del task;
  • submit(): Invia un task al pool, ma non blocca il thread corrente durante l'attesa del completamento del task. Può essere utilizzato per inviare task e recuperare i relativi oggetti Future;
  • execute(): Esegue un task nel pool, ma non restituisce un risultato e non blocca il thread corrente.

Come possiamo avviare un'attività utilizzando ForkJoinPool, molto semplice!

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)); }

Ma qual è la differenza tra l'esecuzione tramite fork(), join() e tramite la classe ForkJoinPool?

È molto semplice! Utilizzando il primo metodo con l'aiuto dei metodi fork(), join(), l'attività viene avviata nello stesso thread in cui questi metodi sono stati chiamati, bloccando questo thread, mentre con l'aiuto della classe ForkJoinPool viene assegnato un thread dal pool e l'attività viene eseguita in questo thread senza bloccare il thread principale.

Esaminiamo più da vicino, supponiamo di avere una tale implementazione:

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!!!"; } }

E vogliamo eseguire questo codice utilizzando fork() e join(). Vediamo cosa verrà stampato sulla console e quale thread eseguirà questa operazione.

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()); } }

E otteniamo questo output sulla console:

Thread: main
Wow, it works!!!

Vediamo ora cosa succede se eseguiamo con 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)); } }

E otteniamo questa conclusione:

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

Come puoi vedere, la differenza è che utilizzando il primo metodo il task viene eseguito nello stesso thread che ha chiamato questo task (thread principale). Ma se utilizziamo il secondo metodo, il thread viene prelevato dal pool di thread ForkJoinPool e il thread principale che ha chiamato questa logica non viene bloccato e prosegue!

Come implementare Fork/Join nel codice

Il modo più semplice per spiegare questo concetto sarebbe tramite un video, piuttosto che fornire 50-80 righe di codice e spiegarle punto per punto.

Note
Nota

ForkJoinPool è efficace per attività che possono essere facilmente suddivise in sotto-attività più piccole. Tuttavia, se le attività sono troppo piccole, l'utilizzo di ForkJoinPool potrebbe non apportare un guadagno significativo in termini di prestazioni.

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 4. Capitolo 2

Chieda ad AI

expand

Chieda ad AI

ChatGPT

Chieda pure quello che desidera o provi una delle domande suggerite per iniziare la nostra conversazione

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

bookForkJoinPool

Scorri per mostrare il menu

La classe ForkJoinPool in Java per lavorare con il framework Fork/Join rappresenta proprio questa realizzazione. Fornisce meccanismi per la gestione di attività che possono essere suddivise in sotto-attività più piccole ed eseguite in parallelo.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool si basa sul concetto di due azioni fondamentali: fork e join. Fork è un'azione in cui un'attività complessa viene suddivisa in sotto-attività più piccole che possono essere eseguite in parallelo. Join è il processo tramite il quale i risultati di queste sotto-attività vengono combinati per formare il risultato dell'attività originale.

Caso di studio

Immagina di dover analizzare un enorme insieme di dati che può essere suddiviso in diversi sottoinsiemi. Se elabori ciascun insieme di dati separatamente e poi unisci i risultati, puoi velocizzare notevolmente l'elaborazione, soprattutto su sistemi multiprocessore.

Come utilizzare ForkJoinPool

Esistono 2 classi per implementare task al loro interno: RecursiveTask e RecursiveAction. Entrambe richiedono l'implementazione del metodo astratto compute(). In RecursiveTask il metodo compute() restituisce un valore, mentre in RecursiveAction il metodo compute() restituisce void.

Main.java

Main.java

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

Quando ereditiamo da RecursiveTask dobbiamo assicurarci di specificare quale tipo di dato il nostro metodo compute() restituirà utilizzando questa sintassi RecursiveTask<String>.

Main.java

Main.java

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

Qui è il contrario, non è necessario specificare esplicitamente il tipo in RecursiveAction, perché il nostro metodo non restituirà nulla e si limiterà a eseguire il compito.

Avvio di un Task

È possibile avviare un task per l'esecuzione anche senza utilizzare ForkJoinPool, semplicemente usando i metodi fork() e join().

È importante notare che il metodo fork() invia il task a un thread. Il metodo join() viene utilizzato per ottenere il risultato.

Main.java

Main.java

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

Metodi Principali di ForkJoinPool

  • invoke(): Avvia un task nel pool e attende il suo completamento. Restituisce il risultato al termine del task;
  • submit(): Invia un task al pool, ma non blocca il thread corrente durante l'attesa del completamento del task. Può essere utilizzato per inviare task e recuperare i relativi oggetti Future;
  • execute(): Esegue un task nel pool, ma non restituisce un risultato e non blocca il thread corrente.

Come possiamo avviare un'attività utilizzando ForkJoinPool, molto semplice!

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)); }

Ma qual è la differenza tra l'esecuzione tramite fork(), join() e tramite la classe ForkJoinPool?

È molto semplice! Utilizzando il primo metodo con l'aiuto dei metodi fork(), join(), l'attività viene avviata nello stesso thread in cui questi metodi sono stati chiamati, bloccando questo thread, mentre con l'aiuto della classe ForkJoinPool viene assegnato un thread dal pool e l'attività viene eseguita in questo thread senza bloccare il thread principale.

Esaminiamo più da vicino, supponiamo di avere una tale implementazione:

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!!!"; } }

E vogliamo eseguire questo codice utilizzando fork() e join(). Vediamo cosa verrà stampato sulla console e quale thread eseguirà questa operazione.

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()); } }

E otteniamo questo output sulla console:

Thread: main
Wow, it works!!!

Vediamo ora cosa succede se eseguiamo con 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)); } }

E otteniamo questa conclusione:

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

Come puoi vedere, la differenza è che utilizzando il primo metodo il task viene eseguito nello stesso thread che ha chiamato questo task (thread principale). Ma se utilizziamo il secondo metodo, il thread viene prelevato dal pool di thread ForkJoinPool e il thread principale che ha chiamato questa logica non viene bloccato e prosegue!

Come implementare Fork/Join nel codice

Il modo più semplice per spiegare questo concetto sarebbe tramite un video, piuttosto che fornire 50-80 righe di codice e spiegarle punto per punto.

Note
Nota

ForkJoinPool è efficace per attività che possono essere facilmente suddivise in sotto-attività più piccole. Tuttavia, se le attività sono troppo piccole, l'utilizzo di ForkJoinPool potrebbe non apportare un guadagno significativo in termini di prestazioni.

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 4. Capitolo 2
some-alt