Pular para conteúdo

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

  1. Uso de Executors.newVirtualThreadPerTaskExecutor(): Cria uma nova Virtual Thread sob demanda para cada tarefa com custo computacional desprezível.
  2. Padrão StructuredTaskScope.ShutdownOnFailure: Coordena as tarefas paralelas garantindo que a falha de uma interrompa imediatamente as demais.
  3. Operações Bloqueantes Transparentes: O Thread.sleep não trava threads físicas de sistema operacional, liberando o processador para outras requisições.

🎯 3. Próximos Passos & Sequência Didática