Virtual Thread Java

De ChuWiki


¿Qué son los Virtual Thread?[editar]

Los hilos tradicionales suelen ser costosos de arrancar y muchas veces están limitados por el sistema operativo, de forma que no se pueden arrancar más de x hilos simultáneamente.

Desde Java 21 tenemos disponibles los Virtual Threads. Son hilos que la propia máquina virtual de Java maneja, sin depender del sistema opertivo. Son más ligeros y el límite de hilos que se pueden lanzar está limitado únicamente a la capacidad de nuestro HW.

Tienes el código de los siguientes ejemplos en VirtualThreadExample.java

Lanzar un Virtual Thread[editar]

El siguiente código de ejemplo lanza un Thread virtual

// Arranque de un Virtual Thread
final Thread virtualThread = Thread.ofVirtual()
        .name("Virtual Thread")
        .start(() ->
                System.out.printf("%s es virtual? %b\n",
                        Thread.currentThread().getName(),
                        Thread.currentThread().isVirtual())
        );
virtualThread.join();

Usando Thread.ofVirtual() obteneos una instancia de un Thread virtual.

Con .name("Virtual Thread") le damos un nombre. Esta llamada es opcional y generalmente sirve solo para depurar nuestro programa, para identificar rápidamente qué hilo está dando el fallo o lo que necesitemos.

Con .start(Runnable r) arrancamos el hilo, pasando como parámetro el Runnable que debe ejecutarse en el hilo. En este ejemplo, el Runnable solo saca por pantalla el nombre del hilo y un booleano indicando si es virtual o no. Para ello se usan los métodos Thread.currentThread().getName() y Thread.currentThread().isVirtual()

Podemos esperar la terminación del hilo virtual con .join()

Una altermantiva más rápida para lanzar el hilo virtual, pero sin nombre, es la siguiente

// Una forma más rápida de arrancar un Virtual Thread, sin nombre
final Thread thread = Thread.startVirtualThread(() ->
        System.out.printf("%s es virtual? %b\n",
                Thread.currentThread().getName(),
                Thread.currentThread().isVirtual())
);
thread.join();

Simplemente llamar a Thread.startVirtualThread() pasando como parámetro nuestro Runnable.

Lanzar un Platform Thread[editar]

Los Platform Thread son los que lanza el sistema operativo, es decir, los tradicionales antes de Java 21. Se pueden lanzar de la forma tradicional

// Arranque de un Thread tradicional, tampoco es virtual
Thread traditionalThread = new Thread(() ->
        System.out.printf("%s es virtual? %b\n",
                Thread.currentThread().getName(), Thread.currentThread().isVirtual())
);
traditionalThread.setName("Traditional Thread");
traditionalThread.start();
traditionalThread.join();

es decir, new Thread() pasando el Runnable como parámetro, poner un nombre si se quiere con setName() y lanzarlo con start()

O bien podemos usar una nueva forma, similar a los Thread virtuales, pero para los de plataforma

// Arranque de un Platform Thread.
final Thread platformThread = Thread.ofPlatform()
        .name("Platform Thread")
        .start(() ->
                System.out.printf("%s es virtual? %b\n",
                        Thread.currentThread().getName(),
                        Thread.currentThread().isVirtual()));
platformThread.join();

El mecanismo es igual, pero en vez de el método ofVirtual(), usamos el método ofPlatform()

Virtual Thread Executors[editar]

Vimos en Ejemplo con ThreadPoolExecutor que suele ser buena idea tener un pool de hilos para reaprovecharlos, evitando así el coste de creación y destrucción de hilos. Y la clase Executors facilitaba esa tarea de la creación del pool de hilos. Revisa el enlace anterior si no te es familiar el concepto de pool de hilos o la clase Executors.

En Java 21, la clase Executors tiene también la posibilidad de crear un pool de hilos virtuales. El código sería el siguente

// Pool de Hilos virtuales
try(ExecutorService executorService = Executors.newFixedThreadPool(10, Thread.ofVirtual().factory())) {
    executorService.execute(()->
            System.out.printf("%s es virtual? %b\n",
                    Thread.currentThread().getName(), Thread.currentThread().isVirtual())
    );
}

El método Executors.newFixedThreadPool() crea un pool de hilos que están disponibles a través de la clase ExecutorService que obtenemos como resultado. Este método admite dos parámetros

  • Número de hilos que queremos en el pool. 10 en este ejemplo.
  • Factoría para crear los hilos. Si no ponemos este parámetro, por defecto serán hilos del sistema oeprativo. Pero si pasamos una factoria, en nuestro caso Thread.ofVirtual().factory(), los hilos se crearán usando esta factoría, es decir, serán virtuales.

La llamada a newFixedThreadPool() se hace dentro de un try-with-resources para asegurar el cierre de todos los hilos una vez terminado de usar ExecutorService.

Pasándole un Runnable a executorService.execute(), cogemos uno de los hilos virtuales disponibles para que ejecute el Runnable.