Threads Virtuais em Java
Por padrão, uma thread Java é executada em uma thread de plataforma, isto é, uma thread fornecida pelo sistema operacional onde a máquina virtual Java (JVM) está em execução. Para muitas aplicações, isso é suficiente. No entanto, as threads de plataforma não são leves: sua criação consome tempo e recursos significativos. Esse custo limita a quantidade de threads que o processador pode gerenciar simultaneamente.
Em aplicações com alta carga — como servidores web que processam milhares de requisições por segundo — esse limite pode se tornar um gargalo. Operações demoradas, como acesso a arquivos, comunicação de rede ou consultas a bancos de dados, podem bloquear threads de plataforma e degradar o desempenho geral do sistema. As threads virtuais foram criadas para resolver exatamente esse problema.
O que são threads virtuais
Threads virtuais são threads leves gerenciadas pela própria JVM, e não diretamente pelo sistema operacional. Elas consomem muito menos recursos, permitindo que uma única JVM suporte milhões de threads virtuais simultaneamente.
Essas threads são ideais para tarefas que passam grande parte do tempo bloqueadas em operações de entrada e saída (I/O), como acesso a rede ou a arquivos. Por outro lado, elas não são adequadas para tarefas que exigem uso intensivo de CPU, pois o ganho de desempenho é pequeno nesse cenário.
Como as threads virtuais funcionam
Uma thread virtual é executada dentro de uma thread de plataforma chamada thread portadora (carrier thread). Quando a thread virtual executa uma operação bloqueante (por exemplo, leitura de um arquivo), a JVM não bloqueia a thread portadora. Em vez disso:
- A JVM suspende a thread virtual,
- Armazena seu estado na memória heap,
- Libera a thread portadora para executar outras tarefas.
Quando a operação bloqueante é concluída, a thread virtual é reatribuída a uma thread portadora disponível e continua sua execução.
Esse mecanismo aumenta significativamente a eficiência: as threads de plataforma nunca ficam ociosas enquanto esperam por I/O, pois estão constantemente executando outras threads virtuais.
De forma simplificada: se um servidor web precisa ler um arquivo para atender uma requisição, a thread virtual que trata essa requisição é destacada do thread portador enquanto o arquivo é lido. O thread portador pode, então, atender outra requisição. Quando a leitura termina, a thread virtual é retomada e continua o processamento.
Assim, o processador é usado de forma muito mais eficiente.
Principais vantagens das threads virtuais
- Escalabilidade: por serem extremamente leves (consomem apenas algumas centenas de bytes), é possível criar milhões de threads virtuais sem sobrecarregar o sistema.
- Simplicidade: o código que usa threads virtuais é escrito da mesma forma que com threads convencionais — não há necessidade de mudar o estilo de programação.
- Compatibilidade: as threads virtuais são implementações da classe
java.lang.Thread, o que garante compatibilidade com bibliotecas e APIs existentes.
Criação de threads virtuais
As threads virtuais utilizam o mesmo API que as threads convencionais. Existem dois principais modos de criá-las:
- Pelo método estático
Thread.startVirtualThread(Runnable task) - Pelo método
Thread.ofVirtual()— um builder que permite configurações adicionais
Método Thread.startVirtualThread()
O método mais simples para criar e iniciar uma thread virtual é Thread.startVirtualThread():
public static Thread startVirtualThread(Runnable task)Exemplo básico:
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
Runnable task = () -> {
System.out.println("Hello from a virtual thread");
};
Thread.startVirtualThread(task);
System.out.println("Main thread finished...");
}
}Nesse exemplo, a thread virtual apenas imprime uma mensagem. É possível que o programa termine antes que a mensagem apareça, pois threads virtuais são daemon threads por padrão, ou seja, threads de segundo plano que não impedem o encerramento da JVM.
Para garantir que o programa espere a conclusão da thread virtual, pode-se chamar join():
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
Runnable task = () -> {
System.out.println("Hello from a virtual thread");
};
var t = Thread.startVirtualThread(task);
try {
t.join(); // aguarda a conclusão da thread virtual
}
catch (InterruptedException _) {
System.out.println("Main thread interrupted");
}
System.out.println("Main thread finished...");
}
}Saída esperada:
Main thread started... Hello from a virtual thread Main thread finished...
Método ofVirtual()
O método ofVirtual() retorna um builder que permite definir propriedades antes de iniciar a thread.
public static Thread.Builder.OfVirtual ofVirtual()O builder oferece métodos como:
Thread.Builder.OfVirtual name(String name)
Thread.Builder.OfVirtual name(String prefix, long start)
Thread start(Runnable task)
Thread unstarted(Runnable task)
ThreadFactory factory()Exemplo:
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
Runnable task = () -> {
System.out.println("Hello from " + Thread.currentThread().getName());
};
var t = Thread.ofVirtual()
.name("MyTask")
.start(task);
try {
t.join();
}
catch (InterruptedException _) {
System.out.println("Main thread interrupted");
}
System.out.println("Main thread finished...");
}
}Saída:
Main thread started... Hello from MyTask Main thread finished...
Também é possível criar a thread sem iniciá-la imediatamente:
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
var t = Thread.ofVirtual()
.name("MyTask")
.unstarted(() -> System.out.println("Hello from " + Thread.currentThread().getName()));
t.start();
try {
t.join();
}
catch (InterruptedException _) {
System.out.println("Main thread interrupted");
}
System.out.println("Main thread finished...");
}
}Propriedades das threads virtuais
Algumas características específicas:
isVirtual()retornatruepara threads virtuais.- Por padrão,
getName()retorna uma string vazia — o nome só aparece se for definido explicitamente. - Threads virtuais são daemon threads, portanto
isDaemon()retornatrue. - O nível de prioridade é fixo (geralmente 5) e não pode ser alterado.
Exemplo:
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
Runnable task = () -> {
Thread t = Thread.currentThread();
System.out.println("Name: " + t.getName());
System.out.println("Is Virtual: " + t.isVirtual());
System.out.println("Is Daemon: " + t.isDaemon());
System.out.println("Priority: " + t.getPriority());
};
var thread = Thread.startVirtualThread(task);
try {
thread.join();
}
catch (InterruptedException _) {
System.out.println("Main thread interrupted");
}
System.out.println("Main thread finished...");
}
}Saída:
Main thread started... Name: Is Virtual: true Is Daemon: true Priority: 5 Main thread finished...
Quando usar e quando evitar threads virtuais
Indicadas para:
- Aplicações I/O-bound — como servidores web, microserviços e sistemas que realizam muitas operações bloqueantes.
- Sistemas de alta concorrência — que precisam lidar com milhares de conexões simultâneas.
Devem ser evitadas em:
- Aplicações CPU-bound — tarefas com alto uso de processador (como cálculos intensivos ou processamento de grandes volumes de dados).
- Seções longas sincronizadas com
synchronized— isso pode “prender” uma thread virtual ao thread portador, reduzindo a eficiência. - Chamadas nativas via JNI — também podem bloquear a thread portadora, causando perda de desempenho.
Resumo
- Threads virtuais são leves e gerenciadas pela JVM.
- Permitem milhões de execuções simultâneas sem sobrecarregar o sistema.
- Usam o mesmo API de
Thread, o que garante compatibilidade. - São ideais para tarefas de entrada e saída (I/O-bound).
- São daemon threads com prioridade fixa.
- Não trazem vantagens para tarefas intensivas de CPU ou operações sincronizadas longas.