La concurrencia es muy crítica para el desarrollo de aplicaciones robustas y escalables que puedan realizar varias operaciones simultáneas. Sin embargo, esto tiene que pagar un precio en términos de sincronización. Incurre en costos de rendimiento debido a los gastos generales que conlleva la adquisición y liberación de cerraduras. Para aliviar estos costos de rendimiento, se han incorporado varias optimizaciones en la JVM, en varios tipos, como bloqueo sesgado, eliminación de bloqueo, engrosamiento de bloqueo y la noción de bloqueos livianos y pesados.
En este artículo, vemos estas optimizaciones con mayor detalle, repasando cómo mejoran la sincronización en aplicaciones Java multiproceso.
Conceptos básicos de bloqueo de Java
En Java, la sincronización de bloques o métodos garantiza que solo un hilo pueda ejecutar una sección crítica de código a la vez. Esto es especialmente importante cuando se considera el intercambio de recursos dentro del entorno multiproceso. Java implementa esto confiando en bloqueos intrínsecos o, a veces, se les llama monitores asociados con objetos o clases que ayudan a administrar el acceso a los subprocesos mediante el uso de bloques sincronizados.
Aunque la sincronización es una necesidad para la seguridad de los subprocesos, puede resultar bastante costosa cuando la contención es baja o está completamente ausente. Aquí es donde las optimizaciones de JVM entran en escena. Por lo tanto, eso reduce el costo del bloqueo y mejorará el rendimiento general.
1. Bloqueo sesgado
¿Qué es el bloqueo sesgado?
El bloqueo sesgado es una optimización dirigida a reducir la sobrecarga de la adquisición de bloqueos. Está optimizado para reducir el costo de adquisición de bloqueos, que está dominado por un solo subproceso o al que se accede en gran medida mediante un solo subproceso. Estos programas a menudo adquieren y liberan bloqueos mediante el mismo subproceso sin competencia de otros subprocesos. La JVM puede reconocer este patrón y desvía el bloqueo hacia ese hilo en particular; La siguiente adquisición del candado es casi gratuita.
¿Cómo funciona el bloqueo sesgado?
Si el bloqueo sesgado está habilitado, la primera vez que un subproceso adquiere un bloqueo, ese bloqueo se desvía hacia ese subproceso. La identidad del subproceso se registra en el encabezado del objeto de bloqueo, y las adquisiciones de bloqueo posteriores por parte de ese subproceso no implican sincronización alguna; simplemente verifican si el bloqueo está sesgado hacia el subproceso actual, lo cual es una operación muy rápida y sin bloqueo. .
Si otro hilo intenta adquirir el bloqueo, entonces el sesgo se cancela y JVM recurre a un mecanismo de bloqueo imparcial estándar. En esta etapa, ahora es un bloqueo estándar y el segundo subproceso tendrá que adquirirlo mediante un proceso de bloqueo estándar.
Beneficios del bloqueo sesgado
Rendimiento: La adquisición del mismo hilo en un bloqueo sesgado es casi una adquisición de bloqueo gratuita.
Por lo tanto, el manejo de contiendas no es necesario porque otros subprocesos no tienen posibilidad de participar en la adquisición del bloqueo.
Reducción de gastos generales: No es necesario cambiar el estado del bloqueo ni modificar los metadatos relacionados con la sincronización, excepto en caso de contención.
¿Cuándo se utiliza el bloqueo sesgado?
El bloqueo sesgado es útil en aplicaciones en las que el mismo subproceso accede principalmente a los bloqueos, como aplicaciones de un solo subproceso o una aplicación que tiene poca contención de bloqueo en subprocesos múltiples. Está habilitado de forma predeterminada en la mayoría de las JVM.
Cómo deshabilitar el bloqueo sesgado
El bloqueo sesgado está habilitado de forma predeterminada, pero también se puede deshabilitar con el indicador JVM como se muestra a continuación:
-XX:-UseBiasedLocking
2. Eliminación de bloqueo
¿Qué es la eliminación de bloqueos?
La eliminación de bloqueos es una optimización muy poderosa en la que la JVM elimina por completo algunas sincronizaciones innecesarias (bloqueos). Inspeccionará el código en busca de oportunidades durante su compilación JIT y descubrirá que la sincronización no es necesaria. Esto suele ocurrir cuando un solo subproceso ha accedido al bloqueo, o el objeto que se utilizará la JVM para sincronizar no comparte el mismo objeto dentro de diferentes subprocesos. Una vez que la JVM considera que ya no es necesario, elimina el bloqueo.
¿Cómo funciona la eliminación de bloqueos?
En la fase de análisis de escape de la compilación JIT, JVM verifica si el objeto está confinado a un solo subproceso o se usa solo en un contexto local. Si la sincronización de ese objeto se puede eliminar porque un objeto no escapa del alcance del hilo que lo creó, entonces así será.
Por ejemplo, si un objeto se crea y se utiliza completamente dentro de un método (y no se comparte entre subprocesos), la JVM se da cuenta de que ningún otro subproceso puede acceder al objeto y, por lo tanto, toda la sincronización es redundante. En tal caso, el compilador JIT simplemente elimina el bloqueo por completo.
Cero gastos generales de bloqueo: Eliminar la sincronización innecesaria también evitará que la JVM pague el costo de adquirir y liberar bloqueos en primer lugar.
Mayor rendimiento: La sincronización inactiva a veces puede generar un mayor rendimiento de la aplicación, especialmente si el código contiene muchos bloques sincronizados.
Echa un vistazo a este fragmento de código:
public void someMethod() { StringBuilder sb = new StringBuilder(); synchronized (sb) { sb.append("Hello"); sb.append("World"); } }
En este caso, la sincronización en sb no es necesaria ya que StringBuilder se usa solo dentro de someMethod y no se comparte entre otros subprocesos. Al observar esto, la JVM puede realizar un análisis de escape para eliminar el bloqueo.
3. Bloqueo de engrosamiento
¿Qué es el engrosamiento de bloqueo?
El engrosamiento del bloqueo es una optimización en la que la JVM expande el alcance de un bloqueo para cubrir más fragmentos de código en lugar de adquirir y liberar continuamente el bloqueo en bucles o pequeñas secciones de código.
Trabajo de engrosamiento de bloqueo
Si la JVM encuentra que un bucle cerrado o múltiples bloques de código adyacentes adquieren y liberan un bloqueo con demasiada frecuencia, puede hacer más grueso el bloqueo sacándolo fuera del bucle o a través de varios bloques de código. Esto hace que la adquisición y liberación repetidas del bloqueo sea costosa y permite que un subproceso mantenga el bloqueo durante más iteraciones.
Ejemplo de código: bloqueo engrosamiento
Considere este fragmento de código:
for (int i = 0; iEl engrosamiento del bloqueo empuja la adquisición del bloqueo fuera del bucle, por lo que el hilo adquiere el bloqueo solo una vez:
synchronized (lock) { for (int i = 0; iLa JVM puede mejorar drásticamente el rendimiento al evitar más adquisiciones y liberaciones del bloqueo.
Beneficios del engrosamiento del bloqueo
Menos libertad en los gastos generales de bloqueo: El engrosamiento evita la adquisición y liberación de bloqueos, especialmente en códigos de puntos de acceso, como bucles que se han iterado miles de veces.
Rendimiento mejorado:
Bloquear por un período más largo mejora el rendimiento en comparación con el escenario en el que, sin bloqueo, dicho bloqueo se adquiriría y liberaría varias veces.4. Candados livianos y pesados
La JVM utiliza dos técnicas de bloqueo diferentes según el grado de contención entre los subprocesos. Estas técnicas incluyen cerraduras ligeras y cerraduras pesadas.
Bloqueo ligero
El bloqueo ligero se produce en ausencia de un bloqueo de contención, lo que significa que solo un hilo está intentando adquirir ese bloqueo. En tales escenarios, la JVM optimiza la adquisición mediante una operación CAS al intentar adquirir el bloqueo, lo que puede ocurrir sin una sincronización pesada.
Bloqueo pesado
En caso de que varios subprocesos quieran obtener el mismo bloqueo; es decir, hay contención, la JVM escala esto a un bloqueo pesado. Esto implicaría bloquear subprocesos a nivel del sistema operativo y administrarlos utilizando primitivas de sincronización a nivel del sistema operativo. Los bloqueos pesados son más lentos porque en realidad requieren que el sistema operativo realice cambios de contexto, así como también administre subprocesos.
Escalamiento de bloqueo
Si surge una disputa en un bloqueo liviano, la JVM puede escalarlo a un bloqueo pesado. La escalada aquí significa cambiar del bloqueo rápido a nivel de usuario a un bloqueo más costoso a nivel de sistema operativo que incluye bloqueo de subprocesos.
Beneficios de las cerraduras ligeras
Adquisición rápida de un candado: Cuando no hay competencia, los candados livianos son mucho más rápidos que los pesados porque evitan la sincronización a nivel del sistema operativo.
Bloqueo reducido: Sin contención, los subprocesos no se bloquean y aumentan linealmente con una latencia más baja.
Desventajas de las cerraduras pesadas
Gastos generales de rendimiento: los bloqueos pesados generan el costo de bloquear subprocesos, cambiar de contexto y activar subprocesos con degradación del rendimiento en regímenes de contención muy altos.
Todas estas optimizaciones ayudan a la JVM a mejorar el rendimiento en aplicaciones multiproceso, de modo que los desarrolladores ahora pueden escribir código seguro y simultáneo sin sacrificar mucho en términos de sobrecarga de sincronización. Comprender estas optimizaciones puede ayudar a los desarrolladores a diseñar sistemas más eficientes, especialmente en casos que tienen una penalización de alto rendimiento por el bloqueo.
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