Los subprocesos virtuales de Java ofrecen una alternativa ligera a los subprocesos del sistema operativo tradicional, lo que permite una gestión eficiente de la concurrencia. Pero comprender su comportamiento es crucial para un rendimiento óptimo. Esta publicación de blog profundiza en la fijación, un escenario que puede afectar la ejecución de subprocesos virtuales, y explora técnicas para monitorearlo y abordarlo.
Los subprocesos virtuales de Java son entidades administradas que se ejecutan sobre los subprocesos del sistema operativo subyacente (subprocesos portadores). Proporcionan una manera más eficiente de manejar la concurrencia en comparación con la creación de numerosos subprocesos del sistema operativo, ya que incurren en una menor sobrecarga. La JVM asigna dinámicamente subprocesos virtuales a subprocesos portadores, lo que permite una mejor utilización de los recursos.
Administrado por JVM: a diferencia de los subprocesos del sistema operativo que son administrados directamente por el sistema operativo, los subprocesos virtuales los crea y programa la máquina virtual Java (JVM). Esto permite un control y una optimización más detallados dentro del entorno JVM.
Reducción de gastos generales: la creación y administración de subprocesos virtuales genera una sobrecarga significativamente menor en comparación con los subprocesos del sistema operativo. Esto se debe a que la JVM puede gestionar un conjunto más grande de subprocesos virtuales de manera eficiente, utilizando una cantidad menor de subprocesos del sistema operativo subyacente.
Compatibilidad con código existente: los subprocesos virtuales están diseñados para integrarse perfectamente con el código Java existente. Se pueden utilizar junto con subprocesos del sistema operativo tradicional y funcionan dentro de construcciones familiares como Executor y ExecutorService para la gestión concurrente.
La siguiente figura muestra la relación entre los subprocesos virtuales y los subprocesos de la plataforma:
La fijación ocurre cuando un hilo virtual queda vinculado a su hilo portador. Básicamente, esto significa que el hilo virtual no puede ser reemplazado (cambiado a otro hilo portador) mientras está en un estado anclado. Estos son escenarios comunes que activan la fijación:
Ejemplo de código:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) throws InterruptedException { final Counter counter = new Counter(); Runnable task = () -> { for (int i = 0; iEn este ejemplo, cuando un hilo virtual ingresa al bloque sincronizado, queda anclado a su hilo portador, pero esto no siempre es cierto. La palabra clave sincronizada de Java por sí sola no es suficiente para provocar la fijación de subprocesos en subprocesos virtuales. Para que se produzca la fijación de subprocesos, debe haber un punto de bloqueo dentro de un bloque sincronizado que haga que un subproceso virtual active el estacionamiento y, en última instancia, no permita el desmontaje de su subproceso portador. La fijación de subprocesos podría provocar una disminución en el rendimiento, ya que anularía los beneficios del uso de subprocesos ligeros/virtuales.
Cada vez que un hilo virtual encuentra un punto de bloqueo, su estado pasa a ESTACIONAMIENTO. Esta transición de estado se indica invocando el método VirtualThread.park():
// JDK core code void park() { assert Thread.currentThread() == this; // complete immediately if parking permit available or interrupted if (getAndSetParkPermit(false) || interrupted) return; // park the thread setState(PARKING); try { if (!yieldContinuation()) { // park on the carrier thread when pinned parkOnCarrierThread(false, 0); } } finally { assert (Thread.currentThread() == this) && (state() == RUNNING); } }Echemos un vistazo a un ejemplo de código para ilustrar este concepto:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { Counter counter = new Counter(); Runnable task = () -> { for (int i = 0; i
El indicador -Djdk.tracePinnedThreads=full es un argumento de inicio de JVM que proporciona información de seguimiento detallada sobre la fijación de subprocesos virtuales. Cuando está habilitado, registra eventos como:
Utilice esta marca con prudencia únicamente durante las sesiones de depuración, ya que introduce una sobrecarga de rendimiento.
Compile nuestro código de demostración:
javac Main.java
Inicie el código compilado con el indicador -Djdk.tracePinnedThreads=full:
java -Djdk.tracePinnedThreads=full Main
Observe el resultado en la consola, que muestra información detallada sobre la fijación de subprocesos virtuales:
Thread[#29,ForkJoinPool-1-worker-1,5,CarrierThreads] java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:183) java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393) java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:621) java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:791) java.base/java.lang.Thread.sleep(Thread.java:507) Counter.increment(Main.java:38)
La fijación es un escenario indeseable que impide el rendimiento de los subprocesos virtuales. Las cerraduras reentrantes sirven como una herramienta eficaz para contrarrestar el bloqueo. Así es como puedes usar bloqueos reentrantes para mitigar situaciones de fijación:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.ReentrantLock; public class Main { public static void main(String[] args) { Counter counter = new Counter(); Runnable task = () -> { for (int i = 0; iEn el ejemplo actualizado, utilizamos un ReentrantLock en lugar de un bloque sincronizado. El hilo puede adquirir el bloqueo y liberarlo inmediatamente después de completar su operación, lo que potencialmente reduce la duración de la fijación en comparación con un bloque sincronizado que podría mantener el bloqueo durante un período más largo.
En conclusión
Los hilos virtuales de Java son un testimonio de la evolución y las capacidades del lenguaje. Ofrecen una alternativa nueva y liviana a los subprocesos del sistema operativo tradicional, proporcionando un puente hacia una gestión eficiente de la concurrencia. Tomarse el tiempo para profundizar y comprender conceptos clave como la fijación de subprocesos puede dotar a los desarrolladores del conocimiento necesario para aprovechar todo el potencial de estos subprocesos livianos. Este conocimiento no solo prepara a los desarrolladores para aprovechar las próximas funciones, sino que también les permite resolver problemas complejos de control de concurrencia de manera más efectiva en sus proyectos actuales.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3