CodeGym /Cursos /JAVA 25 SELF /Creación de hilos virtuales: Thread.ofVirtual().start()

Creación de hilos virtuales: Thread.ofVirtual().start()

JAVA 25 SELF
Nivel 57, Lección 1
Disponible

1. Cómo crear hilos virtuales en la práctica

¡Hora de pasar de la teoría a la práctica! Ya sabes cómo se crea un hilo a la manera clásica:

Thread t = new Thread(() -> System.out.println("Hello from thread!"));
t.start();

O un poco más corto:

new Thread(() -> System.out.println("Hi!")).start();

Ahora, con Java 21, tenemos una nueva forma:

Thread.startVirtualThread(() -> System.out.println("Hello from virtual thread!"));

o de forma más explícita:

Thread t = Thread.ofVirtual().start(() -> System.out.println("Hello from virtual thread!"));

¿Cuál es la diferencia?

  • Thread.ofVirtual().start(...) crea un hilo virtual (Virtual Thread), gestionado por la JVM y no por el SO.
  • Thread.ofPlatform().start(...) (o new Thread(...)) — un hilo de plataforma clásico, como antes.

¿Por qué es importante?

Se pueden crear hilos virtuales por decenas de miles sin temor a OutOfMemoryError. Ahora, si de repente decides procesar un millón de peticiones, Java dirá: «¡Sin problema, trae más!»

2. Sintaxis para crear un hilo virtual

Ejemplo básico:

public class VirtualThreadDemo {
    public static void main(String[] args) {
        Thread thread = Thread.ofVirtual().start(() -> {
            System.out.println("¡Hola desde un hilo virtual! Hilo: " + Thread.currentThread());
        });
        // Esperamos a que termine el hilo (para que main no finalice antes)
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

¿Qué está pasando?

  • Creamos un hilo virtual mediante Thread.ofVirtual().start(...).
  • Dentro del hilo hay una acción sencilla: imprimir un mensaje.
  • Al final llamamos a thread.join(), para que el hilo principal espere a que finalice el virtual (de lo contrario, el programa podría terminar antes de que el hilo alcance a imprimir algo).

Atención:
Un hilo virtual se ve y se comporta casi como uno normal, pero por dentro — ¡magia de la JVM!

3. Creación masiva de hilos virtuales: la fuerza de Loom en la práctica

Y ahora probemos algo que con hilos normales sería arriesgado (o simplemente imposible): vamos a crear 10_000 hilos virtuales, cada uno de los cuales imprimirá su 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("Hilo virtual #" + threadNum + " en funcionamiento!");
            });
        }

        // Esperamos a que terminen todos los hilos
        for (Thread t : threads) {
            t.join();
        }
        System.out.println("¡Todos los hilos virtuales han terminado!");
    }
}
  • Para los hilos clásicos (new Thread(...)), este código casi con total seguridad hará que tu programa se caiga con OutOfMemoryError.
  • Para los hilos virtuales, ¡este es el modo normal! La JVM gestionará con facilidad miles y decenas de miles de hilos.

Por cierto, si te parece que 10_000 es mucho, prueba 100_000 o incluso 1_000_000. En una máquina moderna, la JVM se las arreglará si tus hilos realizan un trabajo sencillo o están esperando E/S.

4. Runnable y expresiones lambda: cómo pasar código a un hilo virtual

Los hilos virtuales aceptan tareas igual que los hilos normales: mediante la interfaz Runnable. Esto significa que puedes pasar expresiones lambda, una referencia a método o cualquier objeto que implemente Runnable.

Ejemplo con lambda:

Thread.ofVirtual().start(() -> System.out.println("¡Lambda en un hilo virtual!"));

Ejemplo con método:

public class TaskRunner {
    public static void main(String[] args) {
        Thread.ofVirtual().start(TaskRunner::doWork);
    }

    static void doWork() {
        System.out.println("Trabajando en un hilo virtual: " + Thread.currentThread());
    }
}

Ejemplo con clase anónima:

Thread.ofVirtual().start(new Runnable() {
    @Override
    public void run() {
        System.out.println("¡Clase anónima en un hilo virtual!");
    }
});

Conclusión:
Todo lo que funcionaba con hilos normales también funciona con los virtuales — solo que ahora es «fácil y rápido».

5. Comparación con ExecutorService: enfoque antiguo y nuevo

ExecutorService clásico

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    int taskNum = i;
    executor.submit(() -> {
        System.out.println("Tarea #" + taskNum + " en ejecución");
    });
}
executor.shutdown();

Problema:
Si hay demasiadas tareas y pocos hilos — las tareas esperan en la cola. Si hay demasiados hilos — el programa se «asfixiará» por falta de recursos.

Nuevo enfoque: Executor sobre hilos virtuales

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 100_000; i++) {
    int taskNum = i;
    executor.submit(() -> {
        System.out.println("Tarea virtual #" + taskNum);
    });
}
executor.shutdown();

¿Qué ocurre?

  • Se crea un hilo virtual independiente por cada tarea.
  • La JVM gestiona su planificación, sin sobrecargar el sistema.
  • No hace falta limitar el tamaño del pool — los hilos virtuales son «casi gratis».

¿Cuándo usar un Executor con hilos virtuales?

  • Cuando necesites lanzar tareas en masa (por ejemplo, procesar un gran número de peticiones, archivos o conexiones de red).
  • Cuando las tareas son independientes y pueden ejecutarse en paralelo.
  • Cuando quieras integrar hilos virtuales en una arquitectura existente en la que ya se use ExecutorService (por ejemplo, en un servidor web, un procesador de tareas, etc.).

Consejo:
Si no estás seguro — empieza con un Executor de hilos virtuales. Es la forma más versátil y moderna.

6. Consejos prácticos: cuándo usar cada cosa

¿Cuándo usar Thread.ofVirtual().start() directamente?

  • Cuando necesites crear un hilo aparte para una tarea única (por ejemplo, para una prueba, una demo o un experimento sencillo).
  • Cuando el número de hilos es pequeño y quieres gestionarlos manualmente.

¿Cuándo usar Executors.newVirtualThreadPerTaskExecutor()?

  • Cuando necesites lanzar tareas en masa (por ejemplo, procesar un gran volumen de peticiones, archivos o conexiones de red).
  • Cuando las tareas son independientes y no requieren coordinación entre sí.
  • Cuando quieras integrar hilos virtuales en una arquitectura existente donde ya se use ExecutorService (por ejemplo, en un servidor web, un procesador de tareas, etc.).

Consejo:
Si no estás seguro — empieza con un Executor de hilos virtuales. Es la forma más versátil y moderna.

7. Manejo de excepciones en hilos virtuales

Los hilos virtuales son hilos normales desde el punto de vista de try-catch. Si dentro de tu Runnable se produce una excepción, no «romperá» toda la JVM; simplemente finalizará ese hilo con error.

Ejemplo:

Thread t = Thread.ofVirtual().start(() -> {
    throw new RuntimeException("¡Algo ha salido mal!");
});
try {
    t.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("El hilo principal siguió ejecutándose.");

En un ExecutorService:
Si envías una tarea mediante submit, puedes obtener el resultado con Future y la excepción se propagará al invocar get():

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Future<?> f = executor.submit(() -> {
    throw new RuntimeException("Error en la tarea virtual");
});
try {
    f.get();
} catch (ExecutionException e) {
    System.out.println("Capturamos el error del hilo virtual: " + e.getCause());
}
executor.shutdown();

8. Errores típicos al crear hilos virtuales

Error n.º 1: confundir hilos virtuales y de plataforma. Si creas hilos con new Thread(...) o Thread.ofPlatform(), no son hilos virtuales. Solo Thread.ofVirtual().start(...) o los métodos de Executors te dan hilos virtuales de verdad.

Error n.º 2: esperar aceleración en cómputo pesado. Los hilos virtuales no aceleran tareas intensivas de CPU (CPU-bound). Si tienes un millón de hilos y cada uno calcula Pi hasta el millonésimo dígito — la JVM no podrá «acelerar» el cálculo; solo conmutará entre hilos.

Error n.º 3: mantener recursos (por ejemplo, bases de datos) por cada hilo. Si creas un millón de hilos virtuales, pero cada uno necesita una conexión independiente a la base, la base no lo soportará. Los hilos virtuales son adecuados para tareas donde la mayor parte del tiempo es de espera (E/S), no para trabajar con recursos externos limitados.

Error n.º 4: no esperar a que terminen los hilos cuando es importante. Si el hilo principal termina antes que los hilos virtuales — el programa puede finalizar sin esperar los resultados. Usa join() o ExecutorService con shutdown() y awaitTermination().

Error n.º 5: usar bibliotecas obsoletas no compatibles con hilos virtuales. Algunas bibliotecas de terceros pueden bloquear hilos a nivel del SO o usar sincronización nativa, lo que reduce la eficacia de los hilos virtuales. Comprueba siempre la compatibilidad.

1
Tarea
JAVA 25 SELF, nivel 57, lección 1
Bloqueada
Telemetría del monoplaza de carreras 🏎️
Telemetría del monoplaza de carreras 🏎️
1
Tarea
JAVA 25 SELF, nivel 57, lección 1
Bloqueada
Gestión del asistente robot experimental 🤖
Gestión del asistente robot experimental 🤖
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION