Command Palette

Search for a command to run...

ES·EN

Nivel 3 · 25 min

Hilos Virtuales

Project Loom (Java 21+) introdujo los hilos virtuales — hilos livianos gestionados por la JVM en lugar del SO. Cambian fundamentalmente cómo diseñás servicios I/O-bound: en lugar de patrones reactivos/asíncronos, podés escribir código bloqueante simple y obtener la escalabilidad de miles de operaciones concurrentes.

Hilos virtuales vs hilos de plataforma

Los hilos de plataforma son wrappers 1:1 alrededor de hilos del SO. Los hilos del SO son costosos (~1MB de stack, overhead de scheduling del kernel) — la mayoría de las JVMs se limitan a unos pocos miles. Los hilos virtuales son M:N: la JVM programa millones de hilos virtuales sobre un pequeño pool de hilos carrier (plataforma). Cuando un hilo virtual se bloquea en I/O, la JVM lo desmonta del hilo carrier (que entonces toma otro hilo virtual), y lo vuelve a montar cuando el I/O se completa.

Thread-per-request e I/O bloqueante

El modelo thread-per-request (un hilo maneja una solicitud de principio a fin) es el modelo de programación más simple pero tradicionalmente requería thread pools porque los hilos del SO son costosos. Con hilos virtuales, thread-per-request se vuelve viable incluso para servicios de alta concurrencia. El I/O bloqueante estándar (JDBC, clientes HTTP, I/O de archivos) funciona de forma transparente — la JVM desmonta automáticamente el hilo virtual durante las llamadas bloqueantes.

Structured Concurrency y Pinning

Structured Concurrency (JEP 453) agrupa hilos virtuales relacionados en un scope — si algún hilo falla, el scope puede cancelar el resto. El pinning es el principal problema: un hilo virtual queda anclado a su carrier cuando llama a código synchronized o JNI. Mientras está anclado, el carrier no puede servir a otros hilos virtuales. La solución: reemplazar synchronized con ReentrantLock. Detectar pinning con -Djdk.tracePinnedThreads=full.

Puntos clave

  • Los hilos virtuales son baratos — creá millones, no los pongas en pools. Los thread pools son un anti-patrón para hilos virtuales.
  • El I/O bloqueante dentro de hilos virtuales es seguro y eficiente. La JVM desmonta el hilo, liberando el carrier para otro trabajo.
  • synchronized ancla los hilos virtuales a los carriers. Reemplazá synchronized con ReentrantLock en librerías que serán llamadas desde hilos virtuales a escala.

Code example

// Crear un hilo virtual (Java 21+)
Thread vt = Thread.ofVirtual().start(() -> {
    // I/O bloqueante está bien aquí
    String result = httpClient.send(request, BodyHandlers.ofString()).body();
    System.out.println(result);
});

// ExecutorService respaldado por hilos virtuales
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000)
        .forEach(i -> executor.submit(() -> doBlockingWork(i)));
} // auto-cierra, espera todas las tareas

// Structured concurrency (preview)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<String> user = scope.fork(() -> fetchUser(id));
    Future<String> order = scope.fork(() -> fetchOrder(id));
    scope.join().throwIfFailed();
    return new Response(user.get(), order.get());
}