1. Lock de peso pesado
En el artículo anterior, presentamos el uso de sincronizados y sus principios de implementación. Ahora debemos saber que Synchronized se implementa a través de un bloqueo de monitor dentro del objeto. Sin embargo, el bloqueo del monitor se implementa esencialmente confiando en el bloqueo mutex del sistema operativo subyacente. El sistema operativo debe cambiar entre subprocesos desde el estado del usuario hasta el estado central. Esto es muy costoso, y la conversión entre los estados lleva un tiempo relativamente largo. Es por eso que sincronizado es ineficiente. Por lo tanto, llamamos a este bloqueo que se basa en la implementación del bloqueo del sistema operativo Mutex Lock "Un bloqueo de peso pesado". El núcleo de las diversas optimizaciones hechas para sincronizar en JDK es reducir el uso de este bloqueo de peso pesado. Después de JDK1.6, para reducir el consumo de rendimiento causado por la obtención y la liberación de cerraduras y mejorar el rendimiento, se introdujeron "cerraduras livianas" y "cerraduras sesgadas".
2. Localización ligera
Hay cuatro tipos de estados de bloqueo: estado sin bloqueo, bloqueo sesgado, bloqueo liviano y bloqueo de peso pesado. Con la competencia de las cerraduras, las cerraduras se pueden actualizar desde cerraduras sesgadas a cerraduras livianas, y luego actualizar las cerraduras de peso pesado (pero la actualización de las cerraduras es unidireccional, lo que significa que solo pueden actualizarse de bajo a alto, y no habrá degradación de cerraduras). En JDK 1.6, el bloqueo de sesgo y el bloqueo liviano están habilitados de forma predeterminada. También podemos deshabilitar el bloqueo de sesgo por -xx: -usebiasedlocking. El estado de bloqueo se guarda en el archivo de encabezado del objeto, tomando el JDK de 32 bits como ejemplo:
Estado de bloqueo | 25 bits | 4 bits | 1 bits | 2 bits | ||
23 bits | 2 bits | ¿Es un bloqueo sesgado? | Bit de bandera de bloqueo | |||
Bloqueo liviano | Puntero a bloquear registros en la pila | 00 | ||||
Cerradura de peso pesado | Puntero a Mutex (bloqueo de peso pesado) | 10 | ||||
Etiquetas GC | nulo | 11 | ||||
Cerradura positiva | ID de hilo | Época | Los sujetos son mayores | 1 | 01 | |
Sin bloqueo | hashcode de objeto | Los sujetos son mayores | 0 | 01 | ||
"Ligero" es relativo a las cerraduras tradicionales que utilizan mutexes del sistema operativo. Sin embargo, es importante enfatizar primero que las cerraduras livianas no se usan para reemplazar las cerraduras de peso pesado. Su intención original es reducir el consumo de rendimiento generado por el uso de cerraduras tradicionales de peso pesado sin competencia múltiple. Antes de explicar el proceso de ejecución de las cerraduras livianas, primero entendemos que los escenarios adaptados a los bloqueos livianos son el caso donde los hilos ejecutan alternativamente bloques sincrónicos. Si se accede al mismo bloqueo al mismo tiempo, el bloqueo liviano se expandirá a un bloqueo de peso pesado.
1. El proceso de bloqueo de bloqueo liviano
(1) Cuando el código entra en el bloque de sincronización, si el estado de bloqueo del objeto de sincronización está libre de bloqueo (el indicador de bloqueo es "01" de estado, ya sea que el bloqueo sesgado sea "0"), la máquina virtual creará primero un espacio llamado registro de bloqueo en la trama de la pila del hilo actual para almacenar la copia actual de la palabra de marca actual del objeto de bloqueo, que se llamó a la palabra de la marca desplazada. En este momento, el estado de la pila de hilo y el encabezado del objeto se muestra en la Figura 2.1.
(2) Copie la palabra de marca en el encabezado del objeto y copie al registro de bloqueo.
(3) Después de que la copia sea exitosa, la máquina virtual utilizará las operaciones CAS para intentar actualizar la palabra de marca del objeto en un puntero para bloquear el registro, y apuntar el puntero del propietario en el registro de bloqueo a la palabra marca del objeto. Si la actualización es exitosa, ejecute el paso (3), de lo contrario, ejecute el paso (4).
(4) Si esta acción de actualización es exitosa, el hilo tiene el bloqueo del objeto y el indicador de bloqueo de la palabra de marca del objeto está configurada en "00", lo que significa que el objeto está en un estado bloqueado liviano. En este momento, el estado de la pila de hilo y el cabezal del objeto se muestra en la Figura 2.2.
(5) Si esta operación de actualización falla, la máquina virtual primero verificará si la palabra marca del objeto apunta a la trama de pila del hilo actual. Si es así, significa que el hilo actual ya tiene el bloqueo del objeto, y luego puede ingresar directamente el bloque de sincronización para continuar la ejecución. De lo contrario, múltiples hilos compiten por cerraduras, y el bloqueo liviano se expandirá a un bloqueo de peso pesado, y el valor de estado de la bandera de bloqueo se convertirá en "10". El puntero al bloqueo de peso pesado (mutex) se almacena en Mark Word, y el hilo que espera el bloqueo también ingresará al estado de bloqueo. El hilo actual intenta usar el giro para adquirir el bloqueo. El giro es para evitar bloquear el hilo y usar un bucle para adquirir el bloqueo.
Figura 2.1 El estado de la pila y el objeto antes de la operación de bloqueo de bloqueo de peso ligero
Figura 2.2 El estado de la pila y el objeto después de la operación de bloqueo de bloqueo liviano
2. Proceso de desbloqueo de bloqueo liviano:
(1) Intente reemplazar el objeto de palabra de marca desplazada copiado en el hilo a través de la operación CAS.
(2) Si el reemplazo es exitoso, se completará todo el proceso de sincronización.
(3) Si el reemplazo falla, significa que otros hilos han intentado adquirir el bloqueo (el bloqueo se ha expandido en este momento), entonces el hilo suspendido debe despertarse mientras libera el bloqueo.
3. Lock positivo
La introducción del bloqueo de polarización es minimizar las rutas innecesarias de ejecución de bloqueo liviano sin competencia de múltiples hilos, porque la adquisición y la liberación de bloqueos livianos dependen de múltiples instrucciones atómicas de CAS, mientras que los bloqueos de sesgo solo deben depender de una forma atómica de CAS al reemplazar el bloqueo de bifeis (el bloqueo de BIAS debe cancelarse una vez que la competencia múltiple de la competencia de rendimiento de la operación de la operación de la operación de la operación de la operación de la operación de la operación de la operación de la operación. Instrucciones atómicas). Como se mencionó anteriormente, los bloqueos livianos se utilizan para mejorar el rendimiento cuando los hilos ejecutan bloques sincrónicos alternativamente, mientras que los bloqueos sesgados se utilizan para mejorar aún más el rendimiento cuando solo un hilo ejecuta bloques sincrónicos.
1. El proceso de adquisición de bloqueo sesgado:
(1) Acceda a si el indicador de la palabra de bloqueo de sesgo está configurado en 1, y si el indicador de bloqueo es 01; confirme que es un estado sesgable.
(2) Si se trata de un estado sesgable, pruebe si el ID del hilo apunta al hilo actual. Si es así, ingrese el paso (5), de lo contrario ingrese el paso (3).
(3) Si la ID de subproceso no apunta al hilo actual, entonces el bloqueo se competirá a través de la operación CAS. Si la competencia es exitosa, configure el ID de subproceso en la palabra marca en la ID de subproceso actual y ejecute (5); Si la competencia falla, ejecute (4).
(4) Si CAS no puede adquirir un bloqueo de parcialidad, significa que hay competencia. Cuando se alcanza el punto SafePoint global, se suspende el hilo que obtiene el bloqueo de sesgo. El bloqueo de sesgo se actualiza a un bloqueo liviano, y el hilo bloqueado en SafePoint continúa ejecutando el código de sincronización.
(5) Ejecutar el código de sincronización.
2. Liberación de bloqueo sesgado:
La revocación del bloqueo sesgado se menciona en el cuarto paso anterior. El bloqueo sesgado solo liberará el bloqueo cuando otros roscas intenten competir por el bloqueo sesgado, y el hilo no liberará activamente el bloqueo sesgado. La cancelación del bloqueo sesgado requiere esperar el punto de seguridad global (no se está ejecutando ningún bytecode en este momento). Primero pausará el hilo con el bloqueo sesgado, determinará si el objeto de bloqueo está en un estado bloqueado y luego volverá al desbloqueado (el bit de bandera es "01") o el bloqueo ligero (el bit de bandera es "00") después de deshacer el bloqueo sesgado.
3. Conversión entre bloqueo de peso pesado, bloqueo liviano y bloqueo de sesgo
Figura 2.3 El diagrama de conversión de los tres
Esta imagen es principalmente un resumen del contenido de arriba. Si tiene una buena comprensión del contenido anterior, la imagen debe ser fácil de entender.
4. Otras optimizaciones
1. Giro adaptativo: del proceso de obtención de cerraduras livianas, sabemos que cuando un hilo no realiza la operación de CAS durante la adquisición de cerraduras livianas, es necesario obtener el bloqueo de peso pesado a través del giro. El problema es que el giro requiere consumo de CPU. Si no se puede obtener el bloqueo, el hilo estará en un estado de giro y desperdiciará los recursos de la CPU en vano. La forma más fácil de resolver este problema es especificar el número de giros, por ejemplo, dejarlo en bicicleta 10 veces e ingresar un estado de bloqueo si no se obtiene el bloqueo. Pero JDK adopta un enfoque más inteligente: giro adaptativo. En pocas palabras, si el hilo tiene éxito, el número de giros será más la próxima vez, y si el giro falla, el número de giros se reducirá.
2. Engrosamiento de bloqueo: el concepto de grosería de bloqueo debe ser más fácil de entender, que es fusionar las operaciones de bloqueo y desbloqueo conectadas varias veces en una vez, expandiendo múltiples cerraduras continuas en una cerradura con un rango más grande. Por ejemplo:
paquete com.paddx.test.string; public class StringBuffertest {StringBuffer StringBuffer = new StringBuffer (); public void append () {StringBuffer.append ("A"); StringBuffer.append ("B"); StringBuffer.append ("C"); }}Aquí, cada vez que llame al método StringBuffer.append, se requiere bloqueo y desbloqueo. Si la máquina virtual detecta una serie de operaciones de bloqueo y desbloqueo en el mismo objeto, la fusionará en una gama más amplia de operaciones de bloqueo y desbloqueo, es decir, el bloqueo se realiza en el primer método de anexo, y el desbloqueo se realiza después de completar el último método de apertura.
3. Eliminación de bloqueo: la eliminación de bloqueo significa eliminar operaciones de bloqueo innecesarias. De acuerdo con la técnica de escape del código, si se determina que un código y los datos en el montón no escapan del hilo actual, entonces se puede considerar que este código es seguro de subprocesos y no hay necesidad de bloquearlo. Mire el siguiente programa:
paquete com.paddx.test.concurrent; public class SynchronizedTest02 {public static void main (string [] args) {sincronizedTest02 test02 = new SynChronizedTest02 (); // Comience el calentamiento para (int i = 0; i <10000; i ++) {i ++; } long start = System.CurrentTimemillis (); para (int i = 0; i <100000000; i ++) {test02.append ("abc", "def"); } System.out.println ("Time =" + (System.CurrentTimemillis () - inicio)); } public void append (string str1, string str2) {stringBuffer sb = new StringBuffer (); sb.append (str1) .append (str2); }}Aunque el anexo de StringBuffer es un método sincrónico, el StringBuffer en este programa pertenece a una variable local y no escapará del método. Por lo tanto, este proceso es en realidad seguro de subprocesos y puede eliminar el bloqueo. Aquí está el resultado de mi ejecución local:
Para minimizar el impacto de otros factores, el bloqueo de sesgo (-xx: -sebiasedlocking) está deshabilitado aquí. A través del programa anterior, se puede ver que el rendimiento ha mejorado enormemente después de que se elimina el bloqueo.
Nota: Los resultados de la ejecución pueden ser diferentes entre las diferentes versiones de JDK. La versión JDK que uso aquí es 1.6.
5. Resumen
Este artículo se centra en la optimización de sincronizado en JDK, como cerraduras livianas y cerraduras sesgadas, pero estas dos cerraduras no están completamente sin deficiencias. Por ejemplo, cuando la competencia es feroz, no solo dejará de mejorar la eficiencia, sino que reducirá la eficiencia, porque hay un proceso de actualización de bloqueo adicional. En este momento, es necesario deshabilitar los bloqueos sesgados a través de -xx: -SebiasedLocking. Aquí hay una comparación de estas cerraduras:
Cerrar | ventaja | defecto | Escenarios aplicables |
Cerradura positiva | El bloqueo y el desbloqueo no requieren un consumo adicional, y hay una brecha de nanosegundos en comparación con realizar un método asincrónico. | Si hay una competencia de bloqueo entre los hilos, causará un consumo adicional de revocación de bloqueo. | Adecuado para escenarios donde solo un hilo accede al bloque sincrónico. |
Bloqueo liviano | Los hilos de la competencia no bloquearán, lo que mejora la velocidad de respuesta del programa. | Si el hilo que nunca obtiene la competencia de bloqueo consumirá la CPU. | Perseguir tiempo de respuesta. La ejecución del bloque síncrono es muy rápida. |
Cerradura de peso pesado | La competencia de hilos no usa Spin y no consume CPU. | Bloqueo de hilos, tiempo de respuesta lento. | Perseguir el rendimiento. La velocidad de ejecución del bloque de sincronización es relativamente larga. |