Concorrência em Java: dos fluxos clássicos às goroutines e async/await
\n\nJava é uma linguagem que, desde o seu nascimento, sabe trabalhar com várias tarefas simultaneamente. Mas o mundo não para. Hoje falaremos sobre concorrência em Java — desde os bons e velhos fluxos (Thread) até os fluxos virtuais (goroutines do Project Loom) e a abordagem assíncrona com async/await (usando CompletableFuture como exemplo).
\n\nSe você programa em Java e quer que seu servidor não "trave" sob carga, enquanto o código permanece legível, este artigo é para você. Vamos nessa!
\n\nPor que a concorrência é importante para o desenvolvedor Java moderno?
\n\nImagine que seu site processa 10.000 requisições por segundo. Se cada requisição for executada sequencialmente, os usuários esperarão uma eternidade. A concorrência em Java permite executar várias tarefas "simultaneamente", utilizando eficientemente os recursos da CPU.
\n\nAplicações modernas não são apenas CRUD. São microsserviços, processamento de fluxos de dados, WebSocket, análise em tempo real. Sem entender o multithreading em Java, você corre o risco de escrever um código que ou falhará com OutOfMemoryError, ou funcionará como uma lesma.
\n\nNível 1: Fluxos clássicos (Thread) — o fundamento
\n\nUm fluxo em Java é a abstração de mais baixo nível para executar código em paralelo. Cada fluxo é um "processo leve" separado dentro do sistema operacional.
\n\nAqui está um exemplo simples de criação de um fluxo:
\n\npublic class ThreadExample {\n public static void main(String[] args) {\n Thread thread = new Thread(() -> {\n for (int i = 0; i < 5; i++) {\n System.out.println("Olá do fluxo: " + i);\n try {\n Thread.sleep(1000);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n }\n }\n });\n thread.start();\n System.out.println("O fluxo principal continua trabalhando");\n }\n}\n\nVantagens:
\n- \n
- Simplicidade de criação — new Thread(() -> { ... }).start(); \n
- Controle direto sobre o ciclo de vida. \n
Desvantagens:
\n- \n
- Criar um fluxo é uma operação cara (sobrecarga de pilha, troca de contexto). \n
- Limitação de quantidade: 10.000 fluxos podem matar sua JVM. \n
- Complexidade de sincronização: deadlock, condição de corrida. \n
A abordagem clássica é boa para tarefas pequenas, mas para aplicações de alta carga não é adequada. É aqui que os fluxos virtuais em Java entram em cena.
\n\nNível 2: Fluxos virtuais (Project Loom) — goroutines em Java
\n\nSe você já ouviu falar sobre goroutines em Go, os fluxos virtuais em Java são quase a mesma coisa. Eles surgiram no Java 21 (Project Loom) e mudam completamente as regras do jogo.
\n\nFluxos virtuais são fluxos "leves", gerenciados pela JVM, e não pelo sistema operacional. Podem ser criados aos milhões e consomem o mínimo de memória.
\n\nExemplo de criação de um fluxo virtual:
\n\npublic class VirtualThreadExample {\n public static void main(String[] args) throws InterruptedException {\n // Criando 100.000 fluxos virtuais\n var threads = new Thread[100_000];\n for (int i = 0; i < threads.length; i++) {\n threads[i] = Thread.startVirtualThread(() -> {\n // Simulando trabalho com I/O bloqueante\n try {\n Thread.sleep(1000);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n }\n });\n }\n for (Thread t : threads) {\n t.join();\n }\n System.out.println("Todos os fluxos virtuais foram concluídos!");\n }\n}\n\nIncrível, não é?