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.
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());
}