Hola Loom!
Thread.startVirtualThread(() -> {
System.out.println("Hello, Loom!");
});
Tools de prueba
|
Desde finales del 2017 se ha introducido una preview, para el uso de hilos virtuales, se supone que con esto daría un giro inesperado al mundo Java 🤔, tanto [1]Ron pressler como [2]Mark Reinhold están manteniéndonos al tanto de como va todo esto.
Una de las razones es el rendimiento que ofrecen, porque no invocan a los hilos del kernel del sistema aka native threads, dado que se crea un contexto especial, al parecer como comenta Ben Evans muy parecido a los antes existentes green threads.
[3]Ben Evans OpenJDK’s Project Loom aims, as its primary goal, to revisit this long-standing implementation and instead enable new Thread objects that can execute code but do not directly correspond to dedicated OS threads. Or, to put it another way, Project Loom creates an execution model where an object that represents an execution context is not necessarily a thing that needs to be scheduled by the OS. Therefore, in some respects, Project Loom is a return to something similar to green threads.
Se podría decir que no es un nuevo concepto, sino otra implementación. |
Project loom vs Project reactor ?
Seguro que Batman y superman nunca trabajaron juntos? 🤙🏿 |
Se decia por ahí, que con project loom no haria falta programación reactiva, pero en realidad, ambas se pueden complementar.
Añadiendo que aun con el uso de hilos virtuales, crear un thread con código no es tan facil de leer, componer, la mayoria de las veces, ahí es un punto fuerte que nos da project reactor.
Olek Dokuka con Andrii Rodionov
Ejecutando un par de hilos virtuales
Hardware para este ejemplo 🔥
|
Vamos a probar la jdk19, pero debemos habilitar una flag en la JVM, y unas configuraciones en el IDE, a la misma vez que eso le dice al compilador que habilite las nuevas features/previews.
- Project Structure
Usar tal cual como la imagen.
⚙ Settings
Esta es la famosa flag
--enable-preview
- Run/Debug Configurations
Usar las opciones siguientes
Añadir también -XX:+AllowRedefinitionToAddDeleteMethods
Un hilo normal del OS
la constante COUNT en realidad equivale a 10 millones de hilos |
@Test
@DisplayName("Executing normal threads")
void normalThread() throws InterruptedException {
var result = new AtomicLong();
var latch = new CountDownLatch(COUNT); (1)
for (int i = 1; i <= COUNT; i++) { (2)
final int index = i;
new Thread(() -> { (3)
result.addAndGet(index);
latch.countDown();
}).start();
}
latch.await(); (4)
log.info("Result: {}", result.get());
assertThat(result.get()).isEqualTo(RESULT);
}
1 | La constante de 10_000_000. |
2 | Un ciclo for del tamaño de esa constante. |
3 | Un hilo típico de toda la vida, fire and forget no apto para producción. |
4 | Este latch sabe la cantidad de tiempo que debe esperar, sin invocar al vulgar get o join. |
Con el Scheduler de Simon Baslé
El fue el autor de este Scheduler llamado boundedElastic, para suplantar al deprecado elastic.
@Test
@DisplayName("Using subscribeOn operator with Schedulers.boundedElastic")
void boundedElastic() throws InterruptedException {
var result = new AtomicLong();
var latch = new CountDownLatch(COUNT);
for (int i = 1; i <= COUNT; i++) {
final int index = i;
Mono.fromSupplier(() -> result.addAndGet(index))
.subscribeOn(Schedulers.boundedElastic()) (1)
.doOnTerminate(latch::countDown)
.subscribe();
}
latch.await();
log.info("Result: {}", result.get());
assertThat(result.get()).isEqualTo(RESULT);
}
1 | Seteando el boundedElastic de esta manera, todo el stream reactivo se afectará por el switcheo de contexto, gracias al operador subscribeOn, obligando el uso de ese Scheduler. |
Caso 1
newVirtualThreadPerTaskExecutor |
@Test
@SneakyThrows
@DisplayName("Executing virtual thread using subscribeOn operator " +
" with Schedulers.fromExecutorService and newVirtualThreadPerTaskExecutor")
void virtualThread() {
var result = new AtomicLong();
var latch = new CountDownLatch(COUNT);
for (int i = 1; i <= COUNT; i++) {
final int index = i;
Mono.fromSupplier(() -> result.addAndGet(index))
.subscribeOn(Schedulers.fromExecutorService(
Executors.newVirtualThreadPerTaskExecutor())) (1)
.doOnTerminate(latch::countDown)
.subscribe();
}
latch.await();
log.info("Result: {}", result.get());
assertThat(result.get()).isEqualTo(RESULT);
}
1 | Seteamos nuestro hilo virtual |
Caso 2
newThreadPerTaskExecutor |
@Test
@SneakyThrows
@DisplayName("Executing virtual thread using subscribeOn operator " +
" with Schedulers.fromExecutorService and newThreadPerTaskExecutor- and factory")
void virtualThread2() {
var result = new AtomicLong();
var latch = new CountDownLatch(COUNT);
for (int i = 1; i <= COUNT; i++) {
final int index = i;
Mono.fromSupplier(() -> result.addAndGet(index))
.subscribeOn(Schedulers.fromExecutorService(Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("newThreadPerTaskExecutor-") (1)
.factory())))
.doOnTerminate(latch::countDown)
.subscribe();
}
latch.await();
log.info("Result: {}", result.get());
assertThat(result.get()).isEqualTo(RESULT);
}
1 | Usando factory para setearlo al contructor un hilo virtual. |
Tabla de resumen
N# Hilos | Tipo de hilo/Scheduler | Tiempo aproximado |
---|---|---|
10 millones |
OS thread/Un Thread Típico |
|
10 millones |
boundedElastic |
|
10 millones |
Caso 1 |
|
10 millones |
Caso 2 + factory |
|
Un stream reactivo común y corriente no se queda tan atrás como me imaginaba 🤔 Interesante!!!
También que viendo los hilos virtuales en un principio parecen generar cierto pico en memoria y CPU, pero no por mucho tiempo eso si 🔥
Que tal con Vaadin ?
Por ejemplo si ajustamos nuestro combo, nos quedaría así
Como medir el Throughput ?
El ejemplo anterior no demuestra mucho e incluso, pareciera que lo hacen igual 🤣de rápido.
Lo ideal seria hacer una prueba de carga mejor, con JMeter, artillery, gatling etc, para ver cuantas request pueden procesar estos paradigmas, se supone que de manera reactiva se procesarian/manejarian mayor cantidad de request, o el doble mejor dicho.
La rapidez de respuesta no es un valor por la cual guiarse.
Parámetros para desplegar en heroku
Para hacer el despliegue en heroku es necesario añadir las flags, también para BlockHound porque tenemos una jdk superior
a la +Jdk13
y se nos queja
Las propiedades las separamos con comas ,
-XX:+AllowRedefinitionToAddDeleteMethods --enable-preview |
Heroku no rula
Personalmente el plan de heroku básico, ya no me llama la atención, y oracle saca una ventaja muy grande, pero eso sera en otro post.