Vídeos novos: segunda, quarta e sexta · 8h

Multithreading em Java: threads, executores e virtual threads

Como executar tarefas em paralelo sem dor de cabeça — do Thread clássico às virtual threads.

Uma thread é uma linha de execução dentro do seu programa. Por padrão, o Java executa o main numa única thread: uma instrução depois da outra. Com multithreading, várias tarefas avançam ao mesmo tempo — útil para aproveitar todos os núcleos do processador ou para não ficar parado esperando uma resposta de rede ou de banco.

Criando a primeira thread

Runnable tarefa = () -> { String nome = Thread.currentThread().getName(); for (int i = 1; i <= 3; i++) { System.out.println(nome + " → passo " + i); } }; Thread t1 = new Thread(tarefa, "T1"); Thread t2 = new Thread(tarefa, "T2"); t1.start(); // start(), não run()! t2.start(); t1.join(); // espera T1 terminar t2.join();

Rode algumas vezes: a ordem das mensagens muda. Isso é normal — quem decide quando cada thread executa é o sistema operacional. E atenção: chamar run() diretamente executa o código na thread atual, sem paralelismo nenhum.

ExecutorService: não crie threads à mão

Criar uma thread nova para cada tarefa é caro. Na prática, usamos um pool de threads que são reaproveitadas:

try (ExecutorService pool = Executors.newFixedThreadPool(4)) { Future<Integer> soma = pool.submit(() -> calcularSoma()); Future<String> relatorio = pool.submit(() -> gerarRelatorio()); System.out.println(soma.get()); // bloqueia até o resultado ficar pronto System.out.println(relatorio.get()); } // o try-with-resources encerra o pool (Java 19+)

O perigo: condição de corrida

Quando duas threads alteram o mesmo dado ao mesmo tempo, o resultado pode ficar errado:

class Contador { private int valor = 0; void incrementar() { valor++; } // ler, somar e gravar: 3 passos! }

Com mil threads chamando incrementar(), o total raramente chega a mil. As soluções mais comuns:

// 1. synchronized: só uma thread por vez entra no método synchronized void incrementar() { valor++; } // 2. classes atômicas: mais simples e rápidas para contadores private final AtomicInteger valor = new AtomicInteger(); void incrementar() { valor.incrementAndGet(); }

Para coleções, prefira as versões preparadas para concorrência, como ConcurrentHashMap.

Virtual threads (Java 21+)

Desde o Java 21, as virtual threads são threads leves geridas pela própria JVM. Você pode criar milhares delas sem esgotar a memória — ideal para aplicações que passam a maior parte do tempo esperando I/O, como APIs que chamam outros serviços.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 10_000; i++) { int id = i; executor.submit(() -> buscarPedido(id)); // uma virtual thread por tarefa } }

No Spring Boot 3.2 ou superior, basta a propriedade spring.threads.virtual.enabled=true para o servidor web atender as requisições com virtual threads.

Resumo: use ExecutorService em vez de criar threads à mão, proteja o estado compartilhado com synchronized ou classes atômicas e considere virtual threads quando o gargalo for espera de I/O.