Use parallel Fork/Join Framework

Este tópico aborda como utilizar o Fork/Join Framework, introduzido no Java 7, mas com grande importância no Java 8, especialmente para processamento paralelo e divisão de tarefas recursivas.


🧠 RESUMO

O Fork/Join Framework é usado para dividir tarefas grandes em subtarefas menores, que são executadas paralelamente. Ideal para algoritmos recursivos e que podem ser paralelizados.

🔧 Baseado na ideia de “divide and conquer”.

  • 🧩 Classe base: java.util.concurrent.ForkJoinPool
  • 🧩 Tarefa base: RecursiveTask<V> (retorna valor) ou RecursiveAction (não retorna)

🔹 CLASSES PRINCIPAIS

ClasseDescrição
ForkJoinPoolExecutor especializado em tarefas que usam fork/join.
RecursiveTask<V>Usa quando a tarefa retorna um valor.
RecursiveActionUsa quando a tarefa não retorna valor.

🔍 EXEMPLO 1 – Usando RecursiveTask para somar um array

import java.util.concurrent.*;

public class SumTask extends RecursiveTask<Long> {
  private final long[] array;
  private final int start, end;
  private static final int THRESHOLD = 3;

  public SumTask(long[] array, int start, int end) {
    this.array = array;
    this.start = start;
    this.end = end;
  }

  @Override
  protected Long compute() {
    if (end - start <= THRESHOLD) {
      long sum = 0;
      for (int i = start; i < end; i++) {
        sum += array[i];
      }
      return sum;
    } else {
      int middle = (start + end) / 2;
      SumTask left = new SumTask(array, start, middle);
      SumTask right = new SumTask(array, middle, end);
      left.fork(); // Executa em paralelo
      long rightResult = right.compute();
      long leftResult = left.join(); // Espera a conclusão da esquerda
      return leftResult + rightResult;
    }
  }

  public static void main(String[] args) {
    ForkJoinPool pool = new ForkJoinPool();
    long[] numbers = {1, 2, 3, 4, 5, 6};
    SumTask task = new SumTask(numbers, 0, numbers.length);
    long result = pool.invoke(task);
    System.out.println("Soma: " + result); // 21
  }
}

🎯 EXEMPLO 2 – RecursiveAction

import java.util.concurrent.RecursiveAction;
import java.util.concurrent.ForkJoinPool;

public class PrintTask extends RecursiveAction {
  private final int start, end;
  private static final int THRESHOLD = 3;

  public PrintTask(int start, int end) {
    this.start = start;
    this.end = end;
  }

  @Override
  protected void compute() {
    if (end - start <= THRESHOLD) {
      for (int i = start; i < end; i++) {
        System.out.println("Imprimindo: " + i);
      }
    } else {
      int mid = (start + end) / 2;
      invokeAll(new PrintTask(start, mid), new PrintTask(mid, end));
    }
  }

  public static void main(String[] args) {
    ForkJoinPool pool = new ForkJoinPool();
    pool.invoke(new PrintTask(0, 10));
  }
}

⚠️ ERROS COMUNS NA PROVA

  1. Usar fork() sem join() – sempre use ambos para obter o resultado da subtarefa.
  2. Esquecer de verificar a condição base (threshold) – isso pode levar à recursão infinita.
  3. Confundir invokeAll() com fork()/join()invokeAll() é uma maneira conveniente de chamar múltiplas subtarefas.
  4. Achar que ForkJoinPool cria threads infinitamente – ele usa um número limitado de threads (pool de workers).

🧠 DICAS PARA A PROVA

  • Use RecursiveTask se precisar de retorno (como soma, contagem, busca).
  • Use RecursiveAction se a tarefa não precisa retornar (como imprimir, escrever arquivo).
  • fork() inicia a execução assíncrona.
  • join() espera a execução terminar e obtém o resultado.
  • O método invoke() do ForkJoinPool inicia a tarefa e bloqueia até terminar.

✅ 10 QUESTÕES DE PRÁTICA

1. O que faz a classe RecursiveTask<V>?

A) Executa threads em paralelo
B) Realiza tarefas que não retornam valor
C) Realiza tarefas com retorno de valor ✅
D) Cria um novo ForkJoinPool


2. Qual é o método usado para executar uma tarefa no ForkJoinPool?

A) run()
B) start()
C) invoke()
D) submit()


3. O que fork() faz?

A) Espera outra thread terminar
B) Executa uma subtarefa de forma assíncrona ✅
C) Cria um novo thread pool
D) Invoca todas as tarefas ao mesmo tempo


4. Qual método é usado para esperar o resultado de uma subtarefa?

A) get()
B) join()
C) await()
D) sleep()


5. Quando devemos usar RecursiveAction?

A) Sempre que o método retorna um valor
B) Quando não há retorno da tarefa ✅
C) Quando usamos threads comuns
D) Nunca, pois está obsoleta


6. ForkJoinPool usa quantas threads por padrão?

A) 1
B) Ilimitadas
C) Número de núcleos disponíveis ✅
D) Sempre 2


7. Qual é a vantagem do Fork/Join Framework?

A) Serialização eficiente
B) Divisão automática e paralelismo ✅
C) Uso de memoria mínimo
D) Evita recursão


8. O que acontece se join() for chamado antes de fork()?

A) Executa corretamente
B) Pode causar deadlock ✅
C) Otimiza a execução
D) Nada muda


9. Qual é o papel do threshold no Fork/Join?

A) Define quantas threads o pool pode usar
B) Define o número de tarefas simultâneas
C) Controla quando parar de dividir e resolver diretamente ✅
D) Controla o tempo máximo de execução


10. invokeAll(t1, t2) faz o quê?

A) Executa t1 e ignora t2
B) Executa t1 e t2 sequencialmente
C) Executa ambas em paralelo e espera as duas terminarem ✅
D) Executa uma thread infinita