Java7 min de leitura

Virtual Threads vs Platform Threads no Java 25

Benchmark Real com PostgreSQL, HikariCP e k6

Virtual Threads

Introdução

Desde o lançamento das Virtual Threads no Java 21 através da JEP 444, muita gente começou a repetir a mesma frase:

"Virtual Threads deixam sua aplicação mais rápida."

Mas será que isso é verdade?

A resposta curta é:

  • depende do workload
  • depende do gargalo
  • e principalmente do que você está medindo

Para entender isso de forma prática, montei um benchmark real comparando:

  • Platform Threads
  • Virtual Threads

Usando:

  • Java 25
  • PostgreSQL
  • HikariCP
  • k6
  • workload HTTP + acesso real ao banco

O objetivo foi medir:

  • throughput
  • latência
  • tail latency (p95)
  • comportamento sob saturação

Repositório do projeto: HttpServer JDK built-in

E se você gostou, me deixe uma estrelinha ⭐


Ambiente do benchmark

Stack utilizada

Tecnologia Versão
Java 25
PostgreSQL Local
HikariCP Pool limitado
k6 Load testing
HttpServer JDK built-in

Arquitetura do teste

O servidor foi implementado usando o HttpServer da própria JDK.

A ideia foi manter:

  • mínimo overhead
  • foco total no modelo de threading
flowchart TD A[Client k6] --> B[HTTP Server JDK HttpServer] B --> C{Executor} C --> D[Virtual Threads
Project Loom] C --> E[Platform Threads
Fixed Thread Pool] D --> F[PostgreSQL] E --> F F --> G[HikariCP
Limited Pool]

Configuração do servidor

&lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">static</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">void</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;startWebServer&lt;/span&gt;&lt;span class=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;(&lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;<span class="hljs-type">boolean</span>&lt;/span&gt; virtual, &lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;<span class="hljs-type">boolean</span>&lt;/span&gt; withLock)&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">throws</span>&lt;/span&gt; IOException {
    &lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;HttpServer&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;httpServer&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; HttpServer.create(&lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">new</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;InetSocketAddress&lt;/span&gt;(&lt;span class=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;<span class="hljs-number">8001</span>&lt;/span&gt;), &lt;span class=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;<span class="hljs-number">0</span>&lt;/span&gt;);

    httpServer.createContext(&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;/caramelo&amp;quot;&lt;/span&gt;, &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">new</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;WebServerHandler&lt;/span&gt;(withLock));

    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">if</span>&lt;/span&gt; (virtual) {
        httpServer.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    } &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">else</span>&lt;/span&gt; {
        httpServer.setExecutor(Executors.newFixedThreadPool(&lt;span class=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;<span class="hljs-number">200</span>&lt;/span&gt;));
    }

    httpServer.start();
}

Simulação de workload real

O benchmark não utilizou apenas Thread.sleep().

Foi utilizado acesso real ao PostgreSQL via JDBC.


Database Service

&lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">public</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;class&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;DatabaseService&lt;/span&gt; {

    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">static</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">final</span>&lt;/span&gt; HikariDataSource dataSource;

    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">static</span>&lt;/span&gt; {
        &lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;<span class="hljs-keyword">var</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;config&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">new</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;HikariConfig&lt;/span&gt;();

        config.setJdbcUrl(&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;jdbc:postgresql:<span class="hljs-comment">//localhost:5432/test&amp;quot;&lt;/span&gt;);</span>
        config.setUsername(&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;postgres&amp;quot;&lt;/span&gt;);
        config.setPassword(&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;password&amp;quot;&lt;/span&gt;);

        &lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment">// gargalo controlado&lt;/span&gt;</span>
        config.setMaximumPoolSize(&lt;span class=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;<span class="hljs-number">100</span>&lt;/span&gt;);

        dataSource = &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">new</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;HikariDataSource&lt;/span&gt;(config);
    }

    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">public</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">static</span>&lt;/span&gt; String &lt;span class=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;query&lt;/span&gt;&lt;span class=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;()&lt;/span&gt; {
		&lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment">// Você pode testar de forma deterministica ou real, direto em uma tabela do Postgres&lt;/span&gt;</span>
		&lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;<span class="hljs-type">boolean</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;deterministic&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-literal&quot;</span>&gt;<span class="hljs-literal">true</span>&lt;/span&gt;;
        &lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;String&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;sql&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; deterministic
                ? &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;SELECT <span class="hljs-title function_">pg_sleep</span><span class="hljs-params">(<span class="hljs-number">0.2</span>)</span>&amp;quot;&lt;/span&gt;
                : &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;SELECT * FROM carro&amp;quot;&lt;/span&gt;;
				
        &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">try</span>&lt;/span&gt; (&lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;Connection&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;conn&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; dataSource.getConnection();
             &lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;<span class="hljs-keyword">var</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;stmt&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; conn.createStatement()) {

            &lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;<span class="hljs-keyword">var</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;rs&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; stmt.executeQuery(sql);

            &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">if</span>&lt;/span&gt; (rs.next()) {
                &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">return</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;OK_DB&amp;quot;&lt;/span&gt;;
            }

        } &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">catch</span>&lt;/span&gt; (Exception e) {
            &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">return</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;ERROR&amp;quot;&lt;/span&gt;;
        }

        &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">return</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;OK_DB&amp;quot;&lt;/span&gt;;
    }
}

Por que limitar o HikariCP?

Essa foi a parte mais importante do benchmark.

Sem um recurso limitado, o teste ficaria artificial.

O pool de conexões foi limitado para:

100 conexões

Isso criou um gargalo real.


Ferramenta de carga

O benchmark foi executado usando k6.

Script utilizado

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;<span class="hljs-regexp">/span&gt; http &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/</span>span&gt; <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;#x27;</span>k6/http<span class="hljs-symbol">&amp;#x27;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>;
<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> { check, sleep } &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;<span class="hljs-regexp">/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;k6&amp;#x27;&lt;/</span>span&gt;;

<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>export<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>const<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> options = {
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-attr&quot;</span>&gt;</span>vus<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;</span>500<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>,
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-attr&quot;</span>&gt;</span>duration<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;#x27;</span>30s<span class="hljs-symbol">&amp;#x27;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>,
};

<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>export<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>default<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>function<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> (<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>) {
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>const<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> res = http.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;get&lt;<span class="hljs-regexp">/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;http:/</span><span class="hljs-regexp">/localhost:8001/</span>caramelo&amp;#x27;&lt;/span&gt;);

  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;</span>check<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>(res, {
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;#x27;</span>status is 200<span class="hljs-symbol">&amp;#x27;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-function&quot;</span>&gt;</span>(<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;</span>r<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>) =<span class="hljs-symbol">&amp;gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> r.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-property&quot;</span>&gt;status&lt;<span class="hljs-regexp">/span&gt; === &lt;span class=&quot;hljs-number&quot;&gt;200&lt;/</span>span&gt;,
  });

  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;</span>sleep<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>(<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;</span>0.1<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>);
}

O que foi medido

Para cada cenário:

  • throughput
  • p95
  • estabilidade
  • comportamento sob concorrência

Os testes foram executados com:

VUs
50
100
200
300
400
500

Resultados consolidados

Model VUs p95 Throughput
Virtual Threads 50 219ms 158 req/s
Virtual Threads 100 220ms 317 req/s
Virtual Threads 200 345ms 460 req/s
Virtual Threads 300 566ms 461 req/s
Virtual Threads 400 841ms 458 req/s
Virtual Threads 500 996ms 460 req/s
Platform Threads 50 344ms 139 req/s
Platform Threads 100 217ms 317 req/s
Platform Threads 200 350ms 458 req/s
Platform Threads 300 1.43s 472 req/s
Platform Threads 400 1.78s 467 req/s
Platform Threads 500 2s 467 req/s

O que os números mostram

1. Throughput ficou praticamente igual

Esse foi o primeiro insight importante.

Mesmo usando Virtual Threads:

o throughput não aumentou drasticamente

Por quê?

Porque o gargalo principal continuou sendo:

  • PostgreSQL
  • HikariCP
  • I/O externo

Ou seja:

Virtual Threads não fazem milagre quando o recurso externo continua limitado.

2. A diferença apareceu na latência de cauda

A partir de ~300 VUs, o comportamento mudou completamente.

Virtual Threads

VUs p95
300 566ms
400 841ms
500 996ms

Crescimento:

  • gradual
  • previsível
  • controlado

Platform Threads

VUs p95
300 1.43s
400 1.78s
500 2s

Aqui a cauda começou a explodir.


3. O comportamento sob saturação

Até ~200 VUs:

os dois modelos se comportaram de forma muito parecida

Isso mostra que:

abaixo da saturação, o modelo de threading importa pouco

Após ~300 VUs

O sistema começou a entrar em fila.

E foi exatamente nesse momento que:

  • scheduler (agendar execução das threads, em Platform Threads o scheduler principal é do: sistema operacional)
  • fairness (justiça na distribuição de execução, ou seja, as requisições recebem tempo de execução de forma equilibrada?)
  • modelo de execução

passaram a importar.

Resumo técnico simples:

Conceito Significado
Scheduler decide quem executa
Fairness quão equilibrada é a distribuição
Starvation threads esquecidas esperando demais

O principal resultado do benchmark

As Virtual Threads não aumentaram significativamente o throughput.

Mas elas:

  • reduziram tail latency
  • reduziram extremos
  • mantiveram maior estabilidade
  • degradaram de forma muito mais previsível

Por que isso acontece?

Platform Threads dependem diretamente de threads do sistema operacional.

Sob alta concorrência:

  • context switch aumenta
  • starvation aparece
  • algumas requisições ficam presas por muito tempo

Resultado:

  • spikes
  • cauda longa
  • latência imprevisível

Já as Virtual Threads

Como são extremamente leves:

  • podem estacionar facilmente
  • liberam carrier threads
  • reduzem contenção pesada do scheduler do SO

Resultado:

  • menor p95
  • menor tail latency
  • distribuição mais homogênea

O benchmark confirmou exatamente a JEP 444

A proposta das Virtual Threads nunca foi:

&amp;quot;fazer CPU ficar mais rápida&amp;quot;

A proposta sempre foi:

melhorar concorrência e escalabilidade de workloads bloqueantes

E foi exatamente isso que apareceu no benchmark.


Conclusão

O benchmark demonstrou algo muito importante:

Virtual Threads não removem gargalos externos.

Se o banco continua limitado:

  • throughput continuará limitado

Mas elas melhoram significativamente:

  • estabilidade
  • fairness
  • tail latency
  • comportamento sob saturação

Resultado final

Característica Virtual Threads Platform Threads
Throughput Similar Similar
Escalabilidade Melhor Pior
Tail latency Muito melhor Muito pior
Estabilidade Alta Média
Degradação sob saturação Suave Agressiva

Considerações finais

O mais interessante desse benchmark é que ele foi executado em um cenário relativamente simples.

Mesmo assim:

  • o comportamento emergente ficou muito claro
  • a saturação apareceu de forma natural
  • e o impacto do modelo de threading ficou evidente

Em workloads:

  • I/O bound
  • bloqueantes
  • altamente concorrentes

Virtual Threads entregam exatamente o que o Loom prometeu:

concorrência massiva com menor custo operacional

Referências

  • JEP 444 - Virtual Threads
  • Java 21/25
  • Project Loom
  • HikariCP
  • PostgreSQL
  • k6

Publicado por: Guilherme Gomes - 10/05/2026 15:08

← Voltar aos Artigos