Una de las actualizaciones de Java 19 de mayor alcance fue la introducción de subprocesos virtuales. Los subprocesos virtuales son parte de Project Loom y han sido oficialmente parte de JVM desde Java 20. Cómo funcionan los subprocesos virtuales Los subprocesos virtuales introducen una capa de abstracción entre los procesos del sistema operativo y la concurrencia a nivel de aplicación. Dicho de otra manera, los subprocesos virtuales se pueden utilizar para programar tareas que organiza la máquina virtual Java, de modo que la JVM media entre el sistema operativo y el programa. La Figura 1 muestra la arquitectura de los hilos virtuales. IDG Figura 1. La arquitectura de subprocesos virtuales en Java. En esta arquitectura, la aplicación crea instancias de subprocesos virtuales y la JVM asigna los recursos informáticos para manejarlos. Compare esto con los subprocesos convencionales, que se asignan directamente a los procesos del sistema operativo (SO). Con los subprocesos convencionales, el código de la aplicación es responsable de aprovisionar y distribuir los recursos del sistema operativo. Con subprocesos virtuales, la aplicación crea instancias de subprocesos virtuales y, por lo tanto, expresa la necesidad de concurrencia. Pero es la JVM la que obtiene y libera los recursos del sistema operativo. Los subprocesos virtuales en Java son análogos a las gorutinas en el lenguaje Go. Cuando se utilizan subprocesos virtuales, la JVM solo puede asignar recursos informáticos cuando los subprocesos virtuales de la aplicación están estacionados, lo que significa que están inactivos y esperando un nuevo trabajo. Esta inactividad es común en la mayoría de los servidores: asignan un subproceso a una solicitud y luego se inactiva, esperando un nuevo evento, como una respuesta de un almacén de datos o más entradas de la red. En cierto modo, el subprocesamiento virtual es una forma sofisticada de agrupación de subprocesos. . Al utilizar subprocesos de Java convencionales, cuando un servidor estaba inactivo ante una solicitud, un subproceso del sistema operativo también estaba inactivo, lo que limitaba gravemente la escalabilidad de los servidores. Como ha explicado Nicolai Parlog, «los sistemas operativos no pueden aumentar la eficiencia de los subprocesos de la plataforma, pero el JDK hará un mejor uso de ellos cortando la relación uno a uno entre sus subprocesos y los subprocesos del sistema operativo». Los esfuerzos anteriores para mitigar los problemas de rendimiento y escalabilidad asociados con los subprocesos Java convencionales incluyen bibliotecas reactivas asíncronas como JavaRX. Los subprocesos virtuales se diferencian en que se implementan a nivel de JVM y, sin embargo, encajan en las construcciones de programación existentes en Java. Las API convencionales, como Executor, se pueden utilizar con subprocesos virtuales. Uso de subprocesos virtuales de Java: una demostración Para esta demostración, he creado una aplicación Java sencilla con el arquetipo Maven. El Listado 1 muestra los cambios que hice en el archivo POM del arquetipo de Maven, configurando el compilador para usar Java 21 y especificando mainClass.Listado 1. El pom.xml para la aplicación de demostración UTF-8 21
21

//…
org.codehaus.mojo
complemento-maven-exec
3.1.0

com.infoworld.App

//…

¡Asegúrese de que haya una JVM con al menos Java 21 disponible! (Si está ejecutando Java 19, puede ejecutar subprocesos virtuales con –preview-enabled=true). Ahora, puede ejecutar el programa (y los siguientes ejemplos) con mvn compile exec:java y las características del subproceso virtual se compilarán y ejecutar. Dos formas de usar subprocesos virtuales Ahora consideremos las dos formas principales en que usará subprocesos virtuales en su código. Si bien los subprocesos virtuales presentan un cambio dramático en el funcionamiento de la JVM, el código en realidad es muy similar al de los subprocesos Java convencionales. La similitud es por diseño y hace que la refactorización de aplicaciones y servidores existentes sea relativamente fácil. Esta compatibilidad también significa que las herramientas existentes para monitorear y observar subprocesos en la JVM funcionarán con subprocesos virtuales. Thread.startVirtualThread(Runnable r)La forma más básica de utilizar un hilo virtual es con Thread.startVirtualThread(Runnable r). Este es un reemplazo para crear una instancia de un hilo y llamar a thread.start(). Considere el código de muestra en el Listado 2. Listado 2. Creación de instancias de un nuevo paquete de subprocesos com.infoworld; importar java.util.Random; Aplicación de clase pública {pública estática vacía principal (cadena[] args ) { vThreads booleanos = args.length > 0; System.out.println( «Usando vThreads: » + vThreads); inicio largo = System.currentTimeMillis(); Aleatorio aleatorio = nuevo Aleatorio(); Ejecutable ejecutable = () -> { doble i = random.nextDouble(1000) % random.nextDouble(1000); }; for (int i = 0; i < 50000; i++){ if (vThreads){ Thread.startVirtualThread(ejecutable); } else { Hilo t = nuevo hilo (ejecutable); t.start(); } } final largo = System.currentTimeMillis(); mucho tiempo transcurrido = finalizar - iniciar; System.out.println("Tiempo de ejecución: " + tiempo transcurrido); } } Cuando se ejecuta con un argumento, el código del Listado 2 utilizará un hilo virtual; en caso contrario, utilizará hilos convencionales. Esto nos permite ver la diferencia fácilmente. El programa genera 50 mil iteraciones de cualquier tipo de hilo que elijas. Luego, hace algunos cálculos simples con números aleatorios y rastrea cuánto tiempo lleva la ejecución. Para ejecutar el código con subprocesos virtuales, escriba: mvn compile exec:java -Dexec.args="true". Para ejecutar con subprocesos estándar, escriba: mvn compile exec:java. Hice una prueba de rendimiento rápida y obtuve los siguientes resultados: Con subprocesos virtuales: Tiempo de ejecución: 174 Con subprocesos convencionales: Tiempo de ejecución: 5450 Estos resultados no son científicos, pero la diferencia en los tiempos de ejecución es sustancial. La experiencia en la línea de comandos es asombrosa, ya que la versión vThread se completa casi instantáneamente. Realmente necesitas probarlo. Hay otras formas de utilizar Thread para generar subprocesos virtuales, como Thread.ofVirtual().start(runnable). Consulte la documentación de subprocesos de Java para obtener más información. Uso de un ejecutor La otra forma principal de iniciar un subproceso virtual es con un ejecutor. Los ejecutores son comunes al tratar con subprocesos y ofrecen una forma estándar de coordinar muchas tareas y agrupaciones de subprocesos. La agrupación no es necesaria con los subprocesos virtuales porque son económicos de crear y eliminar y, por lo tanto, la agrupación es innecesaria. En su lugar, puede pensar en la JVM como si administrara el grupo de subprocesos por usted. Sin embargo, muchos programas utilizan ejecutores, por lo que Java 19 incluye un nuevo método de vista previa en los ejecutores para facilitar la refactorización de subprocesos virtuales. El Listado 3 muestra el nuevo método junto con el antiguo. Listado 3. Nuevos métodos ejecutores // Nuevo método: ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); // Método antiguo: ExecutorService executor = Executors.newFixedThreadPool(Integer poolSize); Además, Java 19 introdujo el método Executors.newThreadPerTaskExecutor (ThreadFactory threadFactory), que puede tomar un ThreadFactory que crea subprocesos virtuales. Una fábrica de este tipo se puede obtener con Thread.ofVirtual().factory(). Mejores prácticas para subprocesos virtuales En general, debido a que los subprocesos virtuales implementan la clase Thread, se pueden usar en cualquier lugar donde estaría un subproceso estándar. Sin embargo, existen diferencias en cómo se deben utilizar los subprocesos virtuales para obtener el mejor efecto. Un ejemplo es el uso de semáforos para controlar la cantidad de subprocesos al acceder a un recurso como un almacén de datos, en lugar de usar un grupo de subprocesos con un límite. Consulte Próximamente a Java 19: subprocesos virtuales y subprocesos de plataforma para obtener más sugerencias. Otra nota importante es que los subprocesos virtuales siempre son subprocesos demonio, lo que significa que mantendrán vivo el proceso JVM que los contiene hasta que se completen. Además, no puede cambiar su prioridad. Los métodos para cambiar la prioridad y el estado del demonio no son operativos. Consulte la documentación de Threads para obtener más información sobre esto. Nuevamente, en general, estas advertencias hacen que los threads virtuales sean más fáciles de manejar para el desarrollador. Una mayor parte del trabajo se empuja hacia la plataforma. La plataforma es el grupo de subprocesos. Refactorización con subprocesos virtuales Los subprocesos virtuales son un gran cambio bajo el capó, pero son intencionalmente fáciles de aplicar a una base de código existente. Los hilos virtuales tendrán el mayor y más inmediato impacto en servidores como Tomcat y GlassFish. Dichos servidores deberían poder adoptar subprocesos virtuales con un mínimo esfuerzo. Esto será efectivamente transparente para los usuarios finales del servidor. Las aplicaciones que se ejecutan en estos servidores obtendrán ganancias de escalabilidad sin ningún cambio en el código, lo que podría tener enormes implicaciones para las aplicaciones a gran escala. Considere una aplicación Java que se ejecuta en muchos servidores y núcleos; de repente, podrá manejar un orden de magnitud de solicitudes más simultáneas (aunque, por supuesto, todo depende del perfil de manejo de solicitudes). Servidores como Tomcat ya permiten subprocesos virtuales. Si tiene curiosidad acerca de los servidores y los subprocesos virtuales, considere esta publicación de blog de Cay Horstmann, donde muestra el proceso de configuración de Tomcat para subprocesos virtuales. Habilita las funciones de vista previa de subprocesos virtuales y reemplaza el Ejecutor con una implementación personalizada que difiere solo en una línea (lo adivinaste, Executors.newThreadPerTaskExecutor). El beneficio de escalabilidad es significativo, como él dice: "Con ese cambio, 200 solicitudes tomaron 3 segundos y Tomcat puede aceptar fácilmente 10,000 solicitudes". Conclusión Los subprocesos virtuales son un cambio importante para la JVM. Para los programadores de aplicaciones, representan una alternativa a la codificación de estilo asincrónico que utiliza técnicas como devoluciones de llamada o futuros. En total, podríamos ver los subprocesos virtuales como un péndulo que regresa hacia un paradigma de programación sincrónica en Java, cuando se trata de concurrencia. Esto es más o menos análogo en estilo de programación (aunque no en absoluto en implementación) a la introducción de async/await en JavaScript. En resumen, escribir un comportamiento asincrónico correcto con una sintaxis síncrona simple se vuelve bastante fácil, al menos en aplicaciones donde los subprocesos pasan mucho tiempo inactivos. Consulte los siguientes recursos para obtener más información sobre los subprocesos virtuales: Copyright © 2023 IDG Communications, Inc.
Source link