1. Como criar threads virtuais na prática
Hora de passar da teoria para a prática! Você já sabe como criar uma thread do jeito clássico:
Thread t = new Thread(() -> System.out.println("Hello from thread!"));
t.start();
Ou um pouco mais curto:
new Thread(() -> System.out.println("Hi!")).start();
Agora, com o Java 21, temos uma nova forma:
Thread.startVirtualThread(() -> System.out.println("Hello from virtual thread!"));
ou de forma mais explícita:
Thread t = Thread.ofVirtual().start(() -> System.out.println("Hello from virtual thread!"));
Qual é a diferença?
- Thread.ofVirtual().start(...) cria uma thread virtual (Virtual Thread), gerenciada pela JVM, não pelo sistema operacional (SO).
- Thread.ofPlatform().start(...) (ou new Thread(...)) — é uma thread de plataforma clássica, como antes.
Por que isso é importante?
É possível criar dezenas de milhares de threads virtuais sem temer OutOfMemoryError. Agora, se você decidir processar um milhão de requisições — o Java vai dizer: “Sem problemas, manda mais!”
2. Sintaxe para criar uma thread virtual
Exemplo básico:
public class VirtualThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.ofVirtual().start(() -> {
System.out.println("Olá do thread virtual! Thread: " + Thread.currentThread());
});
// Aguardamos o término da thread (para o main não terminar antes)
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
O que está acontecendo?
- Criamos uma thread virtual via Thread.ofVirtual().start(...).
- Dentro da thread — uma ação simples: imprimir uma mensagem.
- No final chamamos thread.join() para que a thread principal espere a conclusão da virtual (caso contrário, o programa pode terminar antes que a thread consiga imprimir algo).
Atenção:
Uma thread virtual parece e se comporta quase como uma thread comum, mas por dentro — é mágica da JVM!
3. Criação em massa de threads virtuais: o poder do Loom na prática
Agora vamos tentar algo que com threads comuns seria arriscado (ou simplesmente impossível): vamos criar 10_000 threads virtuais, cada uma imprimindo o seu número.
public class VirtualThreadMassive {
public static void main(String[] args) throws InterruptedException {
int N = 10_000;
Thread[] threads = new Thread[N];
for (int i = 0; i < N; i++) {
int threadNum = i;
threads[i] = Thread.ofVirtual().start(() -> {
System.out.println("Thread virtual #" + threadNum + " está em execução!");
});
}
// Aguardamos a conclusão de todas as threads
for (Thread t : threads) {
t.join();
}
System.out.println("Todas as threads virtuais foram concluídas!");
}
}
- Para threads comuns (new Thread(...)), tal código quase certamente “derrubará” seu programa com OutOfMemoryError.
- Para threads virtuais — isso é operação normal! A JVM lida facilmente com milhares e dezenas de milhares de threads.
Aliás, se 10_000 parecer muito, experimente 100_000 ou até 1_000_000. Em uma máquina moderna a JVM dá conta, desde que suas threads façam um trabalho simples ou fiquem aguardando E/S.
4. Runnable e expressões lambda: como passar código para a thread virtual
Threads virtuais recebem tarefas da mesma forma que as threads comuns: via a interface Runnable. Isso significa que você pode passar expressões lambda, referência a método e qualquer objeto que implemente Runnable.
Exemplo com lambda:
Thread.ofVirtual().start(() -> System.out.println("Lambda em thread virtual!"));
Exemplo com método:
public class TaskRunner {
public static void main(String[] args) {
Thread.ofVirtual().start(TaskRunner::doWork);
}
static void doWork() {
System.out.println("Trabalhando em thread virtual: " + Thread.currentThread());
}
}
Exemplo com classe anônima:
Thread.ofVirtual().start(new Runnable() {
@Override
public void run() {
System.out.println("Classe anônima em thread virtual!");
}
});
Conclusão:
Tudo o que funcionava com threads comuns também funciona com threads virtuais — só que agora é “leve e rápido”.
5. Comparação com ExecutorService: abordagem antiga e nova
ExecutorService clássico
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
int taskNum = i;
executor.submit(() -> {
System.out.println("Tarefa #" + taskNum + " em execução");
});
}
executor.shutdown();
Problema:
Se houver tarefas demais e poucas threads — as tarefas ficam esperando na fila. Se houver threads demais — o programa “engasga” por falta de recursos.
Nova abordagem: Executor com threads virtuais
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 100_000; i++) {
int taskNum = i;
executor.submit(() -> {
System.out.println("Tarefa virtual #" + taskNum);
});
}
executor.shutdown();
O que acontece?
- Para cada tarefa é criada uma thread virtual.
- A própria JVM gerencia o agendamento, sem sobrecarregar o sistema.
- Não é preciso limitar o tamanho do pool — threads virtuais são “quase gratuitas”.
Quando usar um Executor com threads virtuais?
- Quando há um grande fluxo de tarefas e você não quer pensar no tamanho do pool.
- Quando as tarefas são independentes e podem executar em paralelo.
- Quando você quer simplicidade: sem gerenciar threads manualmente.
6. Dicas práticas: quando usar cada opção
Quando usar Thread.ofVirtual().start() diretamente?
- Quando você precisa criar uma thread separada para uma tarefa única (por exemplo, para teste, demonstração ou experimento simples).
- Quando o número de threads é pequeno e você quer gerenciá-las manualmente.
Quando usar Executors.newVirtualThreadPerTaskExecutor()?
- Quando você precisa executar tarefas em massa (por exemplo, processar grande volume de requisições, arquivos, conexões de rede).
- Quando as tarefas são independentes e não requerem coordenação entre si.
- Quando você quer integrar threads virtuais a uma arquitetura existente onde o ExecutorService já é usado (por exemplo, em um servidor web, em um processador de tarefas etc.).
Dica:
Se não tiver certeza — comece com um Executor baseado em threads virtuais. É a forma mais universal e moderna.
7. Tratamento de exceções em threads virtuais
Threads virtuais são threads normais do ponto de vista de try-catch. Se dentro do seu Runnable ocorrer uma exceção, ela não “derruba” toda a JVM; apenas finaliza aquela thread com erro.
Exemplo:
Thread t = Thread.ofVirtual().start(() -> {
throw new RuntimeException("Algo deu errado!");
});
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A thread principal continuou a execução.");
No ExecutorService:
Se você enviar a tarefa via submit, poderá obter o resultado via Future, e a exceção será propagada ao chamar get():
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Future<?> f = executor.submit(() -> {
throw new RuntimeException("Erro na tarefa virtual");
});
try {
f.get();
} catch (ExecutionException e) {
System.out.println("Capturamos o erro do thread virtual: " + e.getCause());
}
executor.shutdown();
8. Erros comuns ao criar threads virtuais
Erro nº 1: Confundir threads virtuais com threads de plataforma. Se você cria threads com new Thread(...) ou Thread.ofPlatform(), isso não são threads virtuais. Somente Thread.ofVirtual().start(...) ou métodos de Executors fornecem Virtual Threads de verdade.
Erro nº 2: Esperar aceleração para cargas pesadas de CPU. Threads virtuais não aceleram tarefas que sobrecarregam o processador (CPU-bound). Se você tiver um milhão de threads, cada uma calculando Pi até a milionésima casa — a JVM não “vai acelerar” o cálculo; ela apenas alternará entre as threads.
Erro nº 3: Manter recursos (por exemplo, banco de dados) por thread. Se você cria um milhão de threads virtuais, mas cada uma precisa de uma conexão separada ao banco — o banco não aguenta. Threads virtuais são ótimas para tarefas em que a maior parte do tempo é de espera (E/S), não de trabalho com recursos externos limitados.
Erro nº 4: Não esperar a conclusão das threads quando isso é importante. Se a thread principal terminar antes das threads virtuais — o programa pode encerrar sem esperar os resultados. Use join() ou ExecutorService com shutdown() e awaitTermination().
Erro nº 5: Usar bibliotecas legadas não compatíveis com threads virtuais. Algumas bibliotecas de terceiros podem bloquear threads no nível do SO ou usar sincronização nativa, o que reduz a eficiência das threads virtuais. Sempre verifique a compatibilidade.
GO TO FULL VERSION