Introdução: dos synchronized básicos às ferramentas modernas
Java é uma das poucas linguagens que, desde o nascimento, possui suporte embutido para multithreading. No entanto, no nível inicial, os desenvolvedores geralmente se limitam à palavra-chave synchronized e a Thread simples. Em sistemas reais de alta carga, isso não é suficiente: bloqueios geram deadlocks, e a criação ingênua de threads leva à sobrecarga da JVM.
Neste artigo, abordaremos mecanismos avançados de concorrência em Java que permitem escrever código multithread eficiente, escalável e seguro. O material é voltado para desenvolvedores que já conhecem os fundamentos do multithreading (monitores, wait/notify, volatile).
Abordaremos quatro tópicos principais: Fork/Join Framework para tarefas recursivas, CompletableFuture para programação assíncrona, StampedLock como alternativa ao ReadWriteLock e o uso adequado de ThreadLocal.
1. Fork/Join Framework: divisão recursiva de tarefas
O Fork/Join Framework surgiu no Java 7 e é projetado para o uso eficiente de processadores multi-core na resolução de problemas que podem ser divididos em subtarefas (dividir para conquistar). A classe principal é ForkJoinPool, que utiliza o mecanismo de "roubo de tarefas" (work-stealing).
1.1. Princípio de funcionamento
Em vez de criar threads manualmente, você descreve uma tarefa como herdeira de RecursiveTask (se houver valor de retorno) ou RecursiveAction (sem retorno). Em seguida, você divide recursivamente a tarefa em partes até que elas se tornem pequenas o suficiente para execução direta.
1.2. Exemplo: soma paralela de array
import java.util.concurrent.RecursiveTask;import java.util.concurrent.ForkJoinPool;
public class SumTask extends RecursiveTask<Long> { private static final int THRESHOLD = 10_000; private final int[] array; private final int start, end;
public SumTask(int[] 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; } int mid = start + (end - start) / 2; SumTask left = new SumTask(array, start, mid); SumTask right = new SumTask(array, mid, end); left.fork(); // inicia a parte esquerda assincronamente long rightResult = right.compute(); // calcula a direita na thread atual long leftResult = left.join(); // aguarda o resultado da esquerda return leftResult + rightResult; }
public static void main(String[] args) { int[] data = new int[1_000_000]; for (int i = 0; i < data.length; i++) data[i] = i;
ForkJoinPool pool = ForkJoinPool.commonPool(); long result = pool.invoke(new SumTask(data, 0, data.length)); System.out.println("Soma: " + result); }}Importante: use ForkJoinPool.commonPool() — ele já é otimizado para o número de núcleos. Evite criar pools próprios sem necessidade.
2. CompletableFuture: assincronicidade sem inferno de callbacks
Antes do Java 8, a programação assíncrona era possível através de Future, mas ele era síncrono (chamada bloqueante get()). O CompletableFuture resolve esse problema, fornecendo uma API funcional poderosa para composição de operações assíncronas.
2.1. Cenários básicos
Você pode criar CompletableFuture, executar tarefas assincronamente e combiná-las em cadeias:
import java.util.concurrent.CompletableFuture;
public cl