Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Aprenda Forkjoinpool | Melhores Práticas de Multithreading
Multithreading em Java

bookForkjoinpool

A classe ForkJoinPool em Java para trabalhar com o framework Fork/Join é justamente a realização disso. Ela fornece mecanismos para gerenciar tarefas que podem ser divididas em subtarefas menores e executadas em paralelo.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

O ForkJoinPool é baseado no conceito de duas ações básicas: fork e join. Fork é uma ação em que dividimos uma tarefa grande em várias subtarefas menores que podem ser executadas em paralelo. Join é o processo pelo qual os resultados dessas subtarefas são combinados para formar o resultado da tarefa original.

Estudo de Caso

Imagine que você precisa analisar um grande conjunto de dados que pode ser dividido em vários conjuntos menores. Se cada conjunto de dados for processado separadamente e, em seguida, os resultados forem mesclados, é possível acelerar significativamente o processamento, especialmente em sistemas multiprocessados.

Como usar ForkJoinPool

Existem 2 classes para implementar tarefas nelas: RecursiveTask e RecursiveAction. Ambas exigem a implementação do método abstrato compute(). Em RecursiveTask, o método compute() retorna um valor, enquanto em RecursiveAction o método compute() retorna void.

Main.java

Main.java

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

Ao herdarmos de um RecursiveTask, é necessário especificar qual tipo de dado o método compute() irá retornar utilizando a seguinte sintaxe: RecursiveTask<String>.

Main.java

Main.java

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

Aqui é o oposto, não é necessário especificar o tipo explicitamente em RecursiveAction, pois o método não irá retornar nenhum valor e apenas executará a tarefa.

Iniciando uma Tarefa

Além disso, é possível iniciar uma tarefa para execução sem utilizar o ForkJoinPool, apenas utilizando os métodos fork() e join().

É importante observar que o método fork() envia a tarefa para algum thread. O método join() é utilizado para obter o resultado.

Main.java

Main.java

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

Principais Métodos do ForkJoinPool

  • invoke(): Inicia uma tarefa no pool e aguarda sua conclusão. Retorna o resultado da conclusão da tarefa;
  • submit(): Envia uma tarefa para o pool, mas não bloqueia o thread atual enquanto aguarda a conclusão da tarefa. Pode ser utilizado para enviar tarefas e recuperar seus objetos Future;
  • execute(): Executa uma tarefa no pool, mas não retorna resultado e não bloqueia o thread atual.

Como iniciar uma tarefa usando ForkJoinPool, muito simples!

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

Mas qual é a diferença entre executar fork(), join() e utilizar a classe ForkJoinPool?

É muito simples! Ao utilizar o primeiro método com o auxílio dos métodos fork(), join(), a tarefa é iniciada na mesma thread em que esses métodos foram chamados, bloqueando essa thread, enquanto com a classe ForkJoinPool é alocado um thread do pool e a tarefa é executada nesse thread sem bloquear a thread principal.

Vamos analisar mais de perto, suponha que temos tal implementação:

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 queremos executar este código utilizando fork() e join(). Vamos ver o que será impresso no console e qual thread executará esta tarefa.

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 obtemos esta saída no console:

Thread: main
Wow, it works!!!

Agora vamos verificar o que acontece se executarmos com 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 chegamos a esta conclusão:

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

E como pode ser observado, a diferença é que ao utilizar o primeiro método a tarefa é executada na mesma thread que chamou essa tarefa (Thread principal). Mas se utilizarmos o segundo método, a thread é obtida do pool de threads ForkJoinPool e a thread principal que chamou essa lógica não é bloqueada e continua executando!

Como implementar Fork/Join no código

A maneira mais fácil de explicar isso seria em um vídeo, em vez de apresentar 50-80 linhas de código e explicar ponto a ponto.

Note
Nota

O ForkJoinPool é eficaz para tarefas que podem ser facilmente divididas em subtarefas menores. No entanto, se as tarefas forem muito pequenas, o uso do ForkJoinPool pode não trazer um ganho de desempenho significativo.

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 4. Capítulo 2

Pergunte à IA

expand

Pergunte à IA

ChatGPT

Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo

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

Deslize para mostrar o menu

A classe ForkJoinPool em Java para trabalhar com o framework Fork/Join é justamente a realização disso. Ela fornece mecanismos para gerenciar tarefas que podem ser divididas em subtarefas menores e executadas em paralelo.

Main.java

Main.java

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

O ForkJoinPool é baseado no conceito de duas ações básicas: fork e join. Fork é uma ação em que dividimos uma tarefa grande em várias subtarefas menores que podem ser executadas em paralelo. Join é o processo pelo qual os resultados dessas subtarefas são combinados para formar o resultado da tarefa original.

Estudo de Caso

Imagine que você precisa analisar um grande conjunto de dados que pode ser dividido em vários conjuntos menores. Se cada conjunto de dados for processado separadamente e, em seguida, os resultados forem mesclados, é possível acelerar significativamente o processamento, especialmente em sistemas multiprocessados.

Como usar ForkJoinPool

Existem 2 classes para implementar tarefas nelas: RecursiveTask e RecursiveAction. Ambas exigem a implementação do método abstrato compute(). Em RecursiveTask, o método compute() retorna um valor, enquanto em RecursiveAction o método compute() retorna void.

Main.java

Main.java

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

Ao herdarmos de um RecursiveTask, é necessário especificar qual tipo de dado o método compute() irá retornar utilizando a seguinte sintaxe: RecursiveTask<String>.

Main.java

Main.java

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

Aqui é o oposto, não é necessário especificar o tipo explicitamente em RecursiveAction, pois o método não irá retornar nenhum valor e apenas executará a tarefa.

Iniciando uma Tarefa

Além disso, é possível iniciar uma tarefa para execução sem utilizar o ForkJoinPool, apenas utilizando os métodos fork() e join().

É importante observar que o método fork() envia a tarefa para algum thread. O método join() é utilizado para obter o resultado.

Main.java

Main.java

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

Principais Métodos do ForkJoinPool

  • invoke(): Inicia uma tarefa no pool e aguarda sua conclusão. Retorna o resultado da conclusão da tarefa;
  • submit(): Envia uma tarefa para o pool, mas não bloqueia o thread atual enquanto aguarda a conclusão da tarefa. Pode ser utilizado para enviar tarefas e recuperar seus objetos Future;
  • execute(): Executa uma tarefa no pool, mas não retorna resultado e não bloqueia o thread atual.

Como iniciar uma tarefa usando ForkJoinPool, muito simples!

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

Mas qual é a diferença entre executar fork(), join() e utilizar a classe ForkJoinPool?

É muito simples! Ao utilizar o primeiro método com o auxílio dos métodos fork(), join(), a tarefa é iniciada na mesma thread em que esses métodos foram chamados, bloqueando essa thread, enquanto com a classe ForkJoinPool é alocado um thread do pool e a tarefa é executada nesse thread sem bloquear a thread principal.

Vamos analisar mais de perto, suponha que temos tal implementação:

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 queremos executar este código utilizando fork() e join(). Vamos ver o que será impresso no console e qual thread executará esta tarefa.

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 obtemos esta saída no console:

Thread: main
Wow, it works!!!

Agora vamos verificar o que acontece se executarmos com 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 chegamos a esta conclusão:

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

E como pode ser observado, a diferença é que ao utilizar o primeiro método a tarefa é executada na mesma thread que chamou essa tarefa (Thread principal). Mas se utilizarmos o segundo método, a thread é obtida do pool de threads ForkJoinPool e a thread principal que chamou essa lógica não é bloqueada e continua executando!

Como implementar Fork/Join no código

A maneira mais fácil de explicar isso seria em um vídeo, em vez de apresentar 50-80 linhas de código e explicar ponto a ponto.

Note
Nota

O ForkJoinPool é eficaz para tarefas que podem ser facilmente divididas em subtarefas menores. No entanto, se as tarefas forem muito pequenas, o uso do ForkJoinPool pode não trazer um ganho de desempenho significativo.

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 4. Capítulo 2
some-alt