Aula 18 - Concorrência de Alto Desempenho com Virtual Threads 🧵
Objetivo Pedagógico
Objetivo: Dominar a maior transformação na concorrência da plataforma Java desde sua concepção: Virtual Threads (Project Loom), concorrência estruturada (StructuredTaskScope) e scoped values, substituindo modelos reativos complexos por código síncrono de altíssima escala.
📑 1. Fundamentos Teóricos & Análise Técnica
Durante décadas, as threads da linguagem Java (java.lang.Thread) mantiveram um mapeamento estrito de 1 para 1 com threads do sistema operacional hospedeiro (Platform Threads). Uma plataforma Linux suporta poucas dezenas de milhares de threads antes de exaurir a memória RAM (cada thread reservava cerca de 1 MB de stack) e sofrer penalidades brutais de chaveamento de contexto (Context Switch).
O Project Loom (Java 21+) introduziu as Virtual Threads: 1. O Modelo M:N do Project Loom: - Virtual Threads são gerenciadas diretamente pelo runtime da JVM, e não pelo kernel do SO. - Uma aplicação Java moderna pode alocar milhões de Virtual Threads simultâneas consumindo apenas algumas centenas de bytes por thread. - Carrier Threads: A JVM mantém um pool fixo de threads de plataforma que carregam as Virtual Threads. Quando uma Virtual Thread executa uma operação bloqueante de I/O (ex: consultar banco relacional JDBC ou chamar API externa), o runtime desmonta (unmounts) a Virtual Thread da Carrier Thread, liberando a thread do sistema para atender outras Virtual Threads. 2. Concorrência Estruturada (StructuredTaskScope): - Organiza subtarefas concorrentes dentro de um mesmo bloco sintático hierárquico. Se uma subtarefa crítica falhar (ex: chamada de frete caiu), as demais subtarefas concorrentes (ex: consulta de estoque) são canceladas imediatamente de forma coordenada (Short-circuiting), eliminando tarefas órfãs e vazamentos de recursos.
📐 Arquitetura Conceitual & Diagrama de Fluxo
graph TD
Tasks["Milhares de Tarefas Concorrentes (ex: 500.000 requisições HTTP)"] --> VThreads["Virtual Threads na JVM (Memória em Bytes)"]
VThreads --> Loom["Project Loom Scheduler (ForkJoinPool)"]
Loom --> Carrier["Poucas Carrier Threads do Sistema Operacional (ex: 8 Threads Físicas)"]
Carrier --> CPU["Processador Físico Multi-Core"]
VThreads -.->|Ao encontrar I/O Bloqueante| Unmount["Desmonta da Carrier Thread Instantaneamente!"]
style Tasks fill:#e1f5fe,stroke:#01579b
style VThreads fill:#fff3e0,stroke:#e65100
style Loom fill:#f3e5f5,stroke:#7b1fa2
style Carrier fill:#e8f5e9,stroke:#2e7d32
style Unmount fill:#ffebee,stroke:#c62828 🔍 Pilares e Diretrizes Técnicas
Nesta unidade, aprofundamos os seguintes conceitos fundamentais: - Morte do Reactive Spaghetti: Retorno ao modelo sequencial limpo (thread-per-request) com rendimento equivalente ou superior a WebFlux/RxJava. - Concorrência Estruturada Cooperativa: Garantia de que tarefas filhas não sobrevivam ao escopo do chamador pai. - Pinning Preventions: Substituição de blocos legados synchronized por ReentrantLock para evitar o travamento (pinning) da Carrier Thread. - Observabilidade Nativa com JFR: Rastreamento de milhões de threads através de eventos do Java Flight Recorder sem impacto na performance.
🛠️ 2. Implementação Prática em Java 21+ Project Loom, Virtual Threads e Structured Concurrency
Abaixo está a implementação técnica de referência, estruturada com padrões de engenharia de software e foco em robustez:
// VirtualThreadsStructuredScope.java (Concorrência Estruturada com Virtual Threads em Java 21)
package com.portal.threads;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.Executors;
import java.time.Duration;
public class VirtualThreadsStructuredScope {
record CustomerDashboard(String profile, String creditScore) {}
// Simulação de chamada externa de perfil
private static String fetchProfile() throws InterruptedException {
Thread.sleep(Duration.ofMillis(200)); // Operação bloqueante segura em Virtual Thread!
return "Perfil: Carlos Silva (VIP)";
}
// Simulação de chamada externa de score
private static String fetchScore() throws InterruptedException {
Thread.sleep(Duration.ofMillis(150));
return "Score: 890";
}
public static CustomerDashboard loadCustomerData() throws Exception {
// Concorrência Estruturada que cancela tudo se uma das tarefas falhar
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
StructuredTaskScope.Subtask<String> profileTask = scope.fork(() -> fetchProfile());
StructuredTaskScope.Subtask<String> scoreTask = scope.fork(() -> fetchScore());
scope.join(); // Aguarda ambas as tarefas concluírem
scope.throwIfFailed(); // Propaga exceção se qualquer uma falhou
return new CustomerDashboard(profileTask.get(), scoreTask.get());
}
}
public static void main(String[] args) throws Exception {
System.out.println("Disparando 10.000 requisições simultâneas em Virtual Threads...");
long start = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> {
try {
loadCustomerData();
} catch (Exception e) {
e.printStackTrace();
}
});
}
} // O try-with-resources aguarda o término de todas as 10.000 tarefas!
long elapsed = System.currentTimeMillis() - start;
System.out.printf("10.000 operações concluídas com sucesso em %d ms!\n", elapsed);
}
}
💡 Análise Passo a Passo do Código
- Uso de
Executors.newVirtualThreadPerTaskExecutor(): Cria uma nova Virtual Thread sob demanda para cada tarefa com custo computacional desprezível. - Padrão
StructuredTaskScope.ShutdownOnFailure: Coordena as tarefas paralelas garantindo que a falha de uma interrompa imediatamente as demais. - Operações Bloqueantes Transparentes: O
Thread.sleepnão trava threads físicas de sistema operacional, liberando o processador para outras requisições.
🎯 3. Próximos Passos & Sequência Didática
-
Slides da Aula
-
Quiz de Fixação
-
Exercícios Práticos
-
Desafio de Projeto