Los sistemas operativos no pueden aumentar la eficiencia de los subprocesos de la plataforma, pero JDK hará un mejor uso de ellos al romper la relación uno a uno entre sus subprocesos y los subprocesos del sistema operativo.
Descarga un PDF de este artículo
Ahora que el JEP 425 de Project Loom ofrece una vista previa oficial de los subprocesos virtuales para Java 19, es hora de analizarlos de cerca: programación y administración de memoria; montar, desmontar, capturar y fijar; observabilidad; y lo que puede hacer para lograr una escalabilidad óptima.
Antes de entrar en hilos virtuales, necesito revisar los hilos clásicos o, como los llamaré de ahora en adelante, hilos de plataforma.
El JDK implementa subprocesos de la plataforma como envolturas delgadas alrededor de los subprocesos del sistema operativo (SO), que son costosos, por lo que no puede tener demasiados. De hecho, la cantidad de subprocesos a menudo se convierte en el factor limitante mucho antes de que se agoten otros recursos, como la CPU o las conexiones de red.
En otras palabras, los subprocesos de la plataforma a menudo limitan el rendimiento de una aplicación a un nivel muy por debajo de lo que podría admitir el hardware.
Ahí es donde entran los hilos virtuales.
Hilos virtuales
Los sistemas operativos no pueden aumentar la eficiencia de los subprocesos de la plataforma, pero JDK puede hacer un mejor uso de ellos cortando la relación uno a uno entre sus subprocesos y los subprocesos del sistema operativo.
Un subproceso virtual es una instancia de java.lang.Thread que requiere un subproceso del sistema operativo para realizar el trabajo de la CPU, pero no retiene el subproceso del sistema operativo mientras espera otros recursos. Verá, cuando el código que se ejecuta en un subproceso virtual llama a una operación de E/S de bloqueo en la API de JDK, el tiempo de ejecución realiza una llamada al sistema operativo sin bloqueo y suspende automáticamente el subproceso virtual hasta que finaliza la operación.
Durante ese tiempo, otros subprocesos virtuales pueden realizar cálculos en ese subproceso del sistema operativo, por lo que lo comparten de manera efectiva.
Fundamentalmente, los subprocesos virtuales de Java incurren en una sobrecarga mínima, por lo que puede haber muchos, muchos, muchos de ellos.
Entonces, así como los sistemas operativos dan la ilusión de una gran cantidad de memoria al asignar un gran espacio de direcciones virtuales a una cantidad limitada de RAM física, el JDK da la ilusión de abundantes subprocesos al asignar muchos subprocesos virtuales a una pequeña cantidad de subprocesos del sistema operativo.
Y así como los programas casi nunca se preocupan por la memoria virtual versus la física, rara vez el código Java concurrente tiene que preocuparse si se ejecuta en un subproceso virtual o en un subproceso de plataforma.
Puede concentrarse en escribir código sencillo y potencialmente bloqueante: el tiempo de ejecución se encarga de compartir los subprocesos del sistema operativo disponibles para reducir el costo del bloqueo a casi cero.
Los subprocesos virtuales admiten variables locales de subprocesos, bloques sincronizados e interrupción de subprocesos; por lo tanto, el código que funciona con Thread y currentThread no tendrá que cambiar. De hecho, esto significa que el código Java existente se ejecutará fácilmente en un subproceso virtual sin cambios ni recompilación.
Una vez que los marcos del servidor ofrecen la opción de iniciar un nuevo subproceso virtual para cada solicitud entrante, todo lo que necesita hacer es actualizar el marco y el JDK y activar el interruptor.
Velocidad, escala y estructura
Es importante comprender para qué sirven los subprocesos virtuales y para qué no.
Nunca olvide que los subprocesos virtuales no son subprocesos más rápidos. Los subprocesos virtuales no ejecutan mágicamente más instrucciones por segundo que los subprocesos de plataforma.
Lo que los subprocesos virtuales son realmente buenos está esperando.
Debido a que los subprocesos virtuales no requieren ni bloquean un subproceso del sistema operativo, potencialmente millones de subprocesos virtuales pueden esperar pacientemente a que finalicen las solicitudes al sistema de archivos, las bases de datos o los servicios web.
Al maximizar la utilización de recursos externos, los subprocesos virtuales proporcionan una mayor escala, no más velocidad. En otras palabras, mejoran el rendimiento.
Más allá de los números concretos, los subprocesos virtuales también pueden mejorar la calidad del código.
Ahora es el momento de explicar cómo funcionan los subprocesos virtuales.
Programación y memoria
El sistema operativo programa los subprocesos del sistema operativo y, por lo tanto, los subprocesos de la plataforma, pero el JDK programa los subprocesos virtuales. El JDK lo hace indirectamente asignando subprocesos virtuales a subprocesos de plataforma en un proceso llamado montaje. El JDK desasigna los hilos de la plataforma más tarde; esto se llama desmontar.
El subproceso de la plataforma que ejecuta un subproceso virtual se denomina subproceso portador y, desde la perspectiva del código Java, el hecho de que un subproceso virtual y su portador compartan temporalmente un subproceso del sistema operativo es invisible. Por ejemplo, los seguimientos de pila y las variables locales de subprocesos están completamente separados.
Luego, los subprocesos del operador se dejan para que el sistema operativo los programe como de costumbre; en lo que respecta al sistema operativo, el subproceso del operador es simplemente un subproceso de plataforma.
Para implementar este proceso, el JDK utiliza un ForkJoinPool dedicado en modo primero en entrar, primero en salir (FIFO) como programador de subprocesos virtuales. (Nota: esto es distinto del grupo común utilizado por flujos paralelos).
De forma predeterminada, el planificador de JDK utiliza tantas plataformas