Forkjoinpool
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
1ForkJoinPool 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
12345678class 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
1234567class 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
12345public 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 objetosFuture;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
12345public 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
12345678class 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
1234567public 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
1234567public 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.
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.
Obrigado pelo seu feedback!
Pergunte à IA
Pergunte à IA
Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo
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
Forkjoinpool
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
1ForkJoinPool 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
12345678class 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
1234567class 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
12345public 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 objetosFuture;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
12345public 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
12345678class 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
1234567public 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
1234567public 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.
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.
Obrigado pelo seu feedback!