Más de 7000 palabras, imágenes y explicaciones lo llevarán a comprender cada detalle de la actualización de bloqueo de Java

Resumen: para la escalada de bloqueo de Java, muchas personas se quedan en una comprensión relativamente superficial. Estas más de 7000 palabras y diagramas le darán una comprensión profunda de cada detalle de la escalada de bloqueo.

Este artículo es compartido por la comunidad HUAWEI CLOUD " ¿Todavía tiene una comprensión superficial de la actualización de bloqueo de Java?" Más de 7000 palabras y diagramas lo llevan a comprender cada detalle de la escalada de bloqueo ", por breakDawn.

Para la escalada de bloqueo de Java, muchas personas se quedan en una comprensión relativamente superficial, lo que puede ser suficiente hasta cierto punto, pero si aprendemos los detalles, ¡podemos comprender mejor cómo lidiar con varios problemas difíciles en la concurrencia de subprocesos múltiples!

Por lo tanto, he integrado la mayoría de los detalles o preguntas que pueden estar involucradas en el proceso de actualización de la cerradura en un artículo. Espero que pueda entender directamente todas las preguntas que dejó cuando aprendió este tema en este artículo.

¿Por qué el cambio de hilo es lento?

El llamado cambio entre el modo de usuario y el modo kernel es solo un aspecto, no el todo. Más importante aún, en una máquina con varias CPU, cuando cambia de subprocesos, significa que es posible que la caché de subprocesos en la CPU original ya no esté disponible . útil Porque cuando el subproceso se vuelve a activar, puede ejecutarse en otra CPU.

Debido a esto, no se recomienda suspender el hilo sin ningún problema, es mucho mejor dejar que el hilo gire que cambiar la CPU después de suspender.

Se basa en este punto que el mecanismo de escalada de bloqueo de sincronización detrás, comprende por qué es necesaria la escalada de bloqueo y luego comprende gradualmente el proceso de escalada de bloqueo.

marca-palabra en el encabezado del objeto

En el encabezado del objeto de cada objeto en Java, hay una marca-palabra de 32 o 64 bits.

Mark-word es una parte importante para comprender el proceso de escalada de bloqueo, y el proceso de escalada de bloqueo posterior estará involucrado, por lo que aquí se brindará una explicación muy detallada. Esta parte solo explica las propiedades que debe tener un objeto (es decir, propiedades que generalmente no desaparecen con el cambio del estado de bloqueo). Los atributos exclusivos de cada estado de bloqueo se explicarán en detalle durante el proceso de escalada de bloqueo.
imagen.png

Indicador de estado de bloqueo + Indicador de bloqueo de polarización (2 bits + 1 bit)

Excepto por el indicador de estado de bloqueo de 2 bits en la palabra clave, los otros 62 bits cambiarán con el cambio del indicador de estado de bloqueo.

Aquí, el estado de bloqueo del objeto actual representado por cada bit indicador de estado de bloqueo se enumera primero. El significado y el principio de funcionamiento de varios bloqueos se explicarán en detalle más adelante.

  • El indicador de estado de bloqueo es 01: pertenece al estado de bloqueo libre o sesgado. Por lo tanto, se requiere un bit de indicador de bloqueo de polarización adicional 1 bit para confirmar si está libre de bloqueo o bloqueo polarizado.
  • El indicador de estado de bloqueo es 00: bloqueo ligero
  • El indicador de estado de bloqueo es 10: bloqueo pesado
  • La bandera de estado de bloqueo es 11: ha sido marcada por gc y está a punto de liberarse

¿Por qué el indicador de bloqueo libre/parcial es 01, pero el indicador de bloqueo ligero es 00?

Es decir, no lock es la situación inicial del estado lock, ¿por qué la bandera no parte de 00?

Una de las explicaciones que encontré en una consulta personal es que, además del bit de indicador de bloqueo, los otros 62 bits del bloqueo ligero son una dirección de puntero.

Si el indicador de bloqueo liviano se establece en 00, luego de determinar que el indicador es 00, m no necesita realizar una operación adicional de markWord>>2, ¡pero puede usar directamente markWord como una dirección! 

Se puede ver desde aquí que el diseñador de jvm todavía es muy detallado y no define arbitrariamente los bits de bandera de cada estado.

código hash (31 bits)

El código hash es fácil de entender. Al almacenar objetos en algún mapa o conjunto, se requiere código hash para confirmar la posición de inserción.

Pero el código hash en el markword sigue siendo diferente del hashCode() que normalmente sobrescribimos.

¿Qué método se utiliza para generar el código hash en markword?

Mucha gente cree erróneamente que el código hash en markword es generado por el método hashcode() que a menudo anulamos.

De hecho, el código hash en markword solo se calcula a partir del código fuente subyacente de JDK C++ (el lado de Java llama al método como System.identityHashCode() ), y se solidifica en markword después de generarse.

Si anula el método hashcode(), se llamará al método cada vez hashCode()para volver a calcular el valor hash.

La razón fundamental es que después de sobrescribir hashcode(), es probable que el método use los miembros modificados para calcular el valor hash, por lo que jvm no se atreve a almacenarlo en la palabra clave.

Por lo tanto, si se anula el método hashcode(), el código hash no se generará en el encabezado del objeto, pero cada vez que se llame al método hashcode()

¿Cuándo se genera el código hash en markword?

Es fácil pensar erróneamente que el objeto se genera tan pronto como se crea.

De hecho, se usa la tecnología de carga diferida , y solo se genera cuando se usa.

Después de todo, es posible que cuando se crea y usa el objeto, no hay necesidad de hacer la operación hash.

¿Adónde va el código hash en otros estados de bloqueo?

Esta pregunta explicará el paradero del código hash en las tres etapas de escalada de bloqueo más adelante. Otros, como la edad generacional, son iguales.

Edad de generación de GC (4 bits)

En el mecanismo de recolección de basura de jvm, una de las bases para determinar cuándo la generación joven ingresa a la generación anterior es confirmar si su edad de generación alcanza el umbral, como se muestra en la siguiente figura.

imagen.png

Se puede ver que la edad de generación es de solo 4 bits, y el valor máximo solo puede ser de 15. Por lo tanto, el umbral de edad que establecemos para ingresar a la vejez -XX:MaxTenuringThreshold solo se puede establecer en 15 como máximo.

cms_gratis

En los bloqueos sin bloqueo y sesgados, también puede ver que hay un cms_free de 1 bit.

De hecho, solo lo utiliza el recopilador de CMS. Pero en el último java11, el colector G1 se usa más, lo que equivale a un uso poco común, por lo que se menciona muy raramente.

De lo anterior se puede ver que solo son necesarios el bit indicador de estado de bloqueo, el código hash, la edad de generación y cms_free, pero desde el diagrama esquemático inicial de markword, el código hash, la edad de generación y cms_free no parecen existir todo el tiempo, así que donde ¿Fueron?¿Paño de lana? El proceso de escalada de bloqueo se explicará en detalle más adelante.

Explicación súper detallada de las cuatro etapas de escalada de bloqueo

sin candado

La marca en el estado sin bloqueo es la siguiente, y puede ver que existe la información mencionada anteriormente

imagen.png

¿Cuáles son las condiciones o el momento de estar en un estado libre de bloqueo?

El estado sin bloqueo se usa cuando el objeto se acaba de crear y aún no ha ingresado al bloque de código sincronizado.

Esto es importante y significa que si no tiene un bloque sincronizado o un método sincronizado, no tendrá bloqueos.

El objeto nunca ingresa al bloque sincronizado, ¿por qué el indicador de bloqueo sesgado es 1?

Como se mencionó en la pregunta anterior, si no se ingresa el bloque de sincronización, no se aplicará el bloqueo de polarización.

Pero si usamos la herramienta Jol de Java para probar e imprimir un nuevo objeto, veremos que los 3 bits inferiores son 101
imagen.png

En realidad, se trata de una optimización agregada más tarde por la JVM. Para cada nuevo objeto, se preestablece un "estado sesgado" , también conocido como estado sesgado anónimo, que realiza la JVM por nosotros durante la inicialización del objeto.

Tenga en cuenta que no hay ThreadID en la parte superior de la marca en este momento, todos los cuales son 0, lo que indica que no hay sesgo de hilo en este momento, por lo que también puede entenderse como sin bloqueo.

La ventaja es que cuando se agrega el bloqueo sesgado en el seguimiento, no hay necesidad de cambiar el bit de indicador de bloqueo sesgado, y solo la identificación del subproceso debe ser cas.

Bloqueo de polarización

Una vez que el código ingresa al bloque de método sincronizado sync por primera vez, es posible pasar de un estado sin bloqueo a un estado de bloqueo sesgado.

Además, muchas personas deben saber que el bloqueo de sesgo solo almacena la identificación del subproceso del sesgo actual, y la actualización se activará solo si la identificación del subproceso es diferente.

¡Pero esta es una declaración muy simplista, y en realidad hay muchos detalles y optimizaciones en el medio! Aquí te lo contamos en detalle.

¿Por qué tener un bloqueo de polarización?

Comprenda esto para comprender los diversos diseños en las cerraduras sesgadas. Suponiendo que nuestro nuevo objeto tiene un método de bloque de código sincronizado, pero solo un subproceso accede a él en todo el ciclo de vida, ¿es necesario realizar acciones competitivas que consumen consumo o incluso introducir una sobrecarga de memoria adicional? No hay necesidad.

Por lo tanto, está dirigido a la escena en la que el objeto tiene llamadas de método síncronas, pero no hay competencia real. 

Una explicación detallada de la palabra clave con polarización de bloqueo

imagen.png

En comparación con la palabra clave sin bloqueo, el indicador de sesgo se convierte en 1, el código hash desaparece y hay una época y una identificación de subproceso.

id de hilo actual en markword

Esta identificación es la identificación del subproceso que ingresó al bloque de código de sincronización de objetos.

La identificación del subproceso de Java es de tipo largo, y es razonable decir que es de 64 bits, pero ¿por qué la identificación del subproceso de tal subproceso es solo de 54 bits?

No se ha encontrado una explicación específica. Puede ser que el equipo de jvm piense que las identificaciones de subprocesos de 54 bits son suficientes para crear tantos subprocesos como 2 ^ 54. Si es necesario crear programas tan frecuentes, el grupo de subprocesos será usado primero.

¿Cómo escribir la identificación del hilo?

¿La identificación del hilo está escrita directamente en la palabra clave? No, debe notar que es posible escribir al mismo tiempo en este momento.

Por lo tanto , la identificación del subproceso se escribirá en el método CAS . En resumen, primero obtenga la identificación del hilo original y luego actualice la identificación del hilo. Después de la actualización, verifique si es consistente con las expectativas. Si es inconsistente, significa que alguien lo ha cambiado. Si la identificación del hilo falla para ser escrito, significa que hay competencia, y se actualiza a peso ligero.

¿A dónde fue el código hash?

Notamos que el código hash cuando está desbloqueado desaparece.

Para el bloqueo sesgado, una vez que el código hash se establece en el encabezado del objeto, el estado de bloqueo sesgado no se ingresará al ingresar al bloque sincronizado, y saltará directamente al bloqueo liviano.Después de todo, no hay lugar para almacenar el código hash. en la cerradura sesgada (las siguientes cerraduras ligeras y pesadas tienen lugares de almacenamiento) 

Por lo tanto, cualquier objeto k que haya realizado operaciones similares a hashmap.put(k,v) sin sobrescribir el código hash omitirá directamente el bloqueo sesgado cuando se bloquee en el futuro.

que es epoca

Mucha gente llama a esta propiedad "marca de tiempo de sesgo", pero rara vez se explica en detalle.

Principalmente porque implica 2 optimizaciones muy importantes en el bloqueo sesgado (re-sesgo por lotes y deshacer por lotes)

Para esta época, a continuación se explica el proceso de desbloqueo de la cerradura sesgada.

Simplemente puede entender que a través de la época, la JVM puede saber si el bloqueo de polarización de este objeto ha expirado. En el caso de vencimiento, se permite intentar adelantarse directamente sin revocar el bloqueo de polarización.

Explicación detallada de la operación de bloqueo de polarización

¿Cómo evitar el conflicto y la competencia cuando se predispone a bloquear?

Sabemos que el bloqueo sesgado en realidad está configurando la identificación del hilo, pero ¿qué pasa si hay un conflicto?

Por lo tanto, jmv configurará la identificación del subproceso de polarización a través de CAS . Una vez que la configuración sea exitosa, el bloqueo de polarización se colgará.

Después de cada acceso, verifique que la identificación del hilo sea consistente e ingrese directamente el bloque de código síncrono para su ejecución.

Suplemento del concepto CAS:

CAS es una operación atómica, la persona que llama debe dar el valor esperado y el valor final de la variable modificada

Cuando el valor de la variable en la memoria es igual al valor esperado, se actualizará al valor final. Esta operación de comparación y actualización igual es una operación atómica.

Para el proceso de bloqueo del bloqueo sesgado, de hecho, primero se elimina la parte de identificación del subproceso. Si está vacía, se realiza la operación CAS (valor esperado: vacío, valor final: identificación de subproceso actual). Si se encuentra que el valor esperado no coincide, significa que ha sido reemplazado.

¿La identificación del subproceso en markword volverá a 0 al salir del bloque sincronizado?

No, esta identificación de subproceso de bloqueo sesgada siempre se bloqueará y, siempre que la identificación se identifique más tarde, no se requiere ningún tratamiento especial.

La operación de corte de bloqueo o actualización cuando compiten los bloqueos sesgados.

Pero cuando hay otros subprocesos a los que acceder, hay un problema con el bloqueo sesgado establecido anteriormente, lo que indica que hay varios subprocesos que acceden al mismo objeto.

¡Aviso! ! ! Esto no es como lo que dicen muchos materiales, una vez que se produce una llamada de subprocesos múltiples, el bloqueo sesgado se actualiza a un bloqueo ligero , pero se realiza una gran cantidad de procesamiento de detalles para evitar la operación de bloqueo ligero que consume CPU tanto como sea posible.

Primero, el jvm tiene en cuenta este escenario:

En la primera 1h, el subproceso A está llamando a una gran cantidad de objetos obj, por lo que el bloqueo sesgado siempre es el subproceso A.

Más tarde, el subproceso A dejó de ejecutarse y la llamada del objeto obj se transfirió al subproceso B, es decir, el subproceso B llamará en el futuro.

Entonces, en este momento, ¿es necesario actualizar la cerradura liviana de inmediato?

¡No hay necesidad! Debido a que el futuro sigue siendo una llamada de un solo subproceso, solo el subproceso es diferente, ¿tal vez pueda intentar usar el bloqueo de polarización?

Entonces, existen las siguientes acciones para revocar el bloqueo sesgado:

  1. Cuando el subproceso B descubre que es un bloqueo sesgado y la identificación del subproceso no es la suya, comienza a deshacer la operación.
  2. Primero, el subproceso B siempre esperará a que el objeto obj alcance el punto seguro de jvm .
  3. Después de llegar al punto seguro, el subproceso B verifica si el subproceso A está dentro del bloque sincronizado de obj.
  4. Si el subproceso A está en un bloque de código sincronizado, no hay negociación y se actualiza directamente a un bloqueo ligero.
  5. Si el subproceso A no está en el bloque de código sincronizado, el subproceso B todavía tiene una oportunidad. Primero cambia el bloqueo sesgado a un estado libre de bloqueo y luego usa CAS para intentar volver a competir. Si puede competir, sesgará sí mismo.

El proceso completo se muestra en la siguiente figura:

imagen.png

¿Por qué esperar un punto seguro antes de realizar una operación de deshacer?

Esto es para garantizar la seguridad de la operación de deshacer. De lo contrario, cuando se revoca el jvm, otro subproceso comienza a operar en el objeto nuevamente, lo que genera un error.

¿Por qué degenerar primero en un estado sin bloqueo y luego intentar competir por un bloqueo sesgado? ¿No puede estar directamente sesgado?

Debido a que no puede predecir si A regresará, después de configurarlo para desbloquearlo, A y B pueden competir de manera justa.

¿Por qué el subproceso de polarización original tiene que actualizarse a un candado ligero cuando está en un bloque de código sincronizado? ¿Se puede revocar sin bloqueo de manera similar para competir?

No, porque si el bloque de código de sincronización aún se está ejecutando, entonces el hilo B está destinado a no poder obtener el bloqueo inmediatamente en este momento. Está destinado a que se debe actualizar a un bloqueo ligero y adquirir el bloqueo a través del bucle. capacidad en la cerradura ligera.

Cambio de polarización por lotes y la aplicación de épocas

Como se mencionó anteriormente, cuando el subproceso B reemplaza el bloqueo sesgado, intentará esperar un punto seguro, revocarlo para que no se bloquee y luego realizar una preferencia justa. Esta acción requiere bastante tiempo.

Supongamos que hay un escenario en el que tenemos 30 objetos obj nuevos, todos los cuales son utilizados por el subproceso A al principio y luego por el subproceso B a través del bucle for, luego se encontrará que en un período de tiempo muy corto, el subproceso parcial la revocación del bloqueo a ningún bloqueo se ha producido continuamente y no hay una actualización ligera debido a la contención del bloque de sincronización.

Luego, el jvm adivina que la situación es similar en este momento, por lo que cuando el subproceso B llama al objeto obj, ya no se revoca, y el CAS compite directamente por el threadId, porque el jvm predice que A no vendrá a agarrar Los pasos específicos son los siguientes:

  1. El jvm define un contador de revocación sesgado y una versión sesgada de época en el objeto de clase del objeto obj .

  2. Cada vez que se revoca un bloqueo de polarización de un objeto, el contador de revocación de polarización es +1.

  3. Una vez que se suma a 20, se considera que se ha producido una revocación de bloqueo a gran escala. Entonces, el valor de época en el objeto de clase es +1 (pero la época generalmente tiene solo 2 bits, 0~3).

  4. A continuación, jvm encontrará todos los objetos obj que están en el bloque de código sincronizado y hará que su época sea igual a la época del objeto de clase.

  5. Otros objetos obj que no están en el bloque de código sincronizado no modifican la época.

  6. Cuando el subproceso B viene de visita y descubre que la época del objeto obj no es igual a la época del objeto de clase, la acción de deshacer ya no se realiza y el CAS se adelanta directamente. Porque cuando la época no es igual, significa que el objeto obj no ha sido utilizado antes por el propietario original, pero sus hermanos se han rendido antes, así que debería tratar de ocuparlo directamente, ¡no hay necesidad de ser tan cauteloso! 

El proceso detallado se muestra en la siguiente figura:
imagen.png

Deshacer en bloque

Sin embargo, si hay más de 40 acciones de revocación de este tipo en un corto período de tiempo, la JVM pensará que este número es demasiado y no está garantizado.

En este momento, el jvm establecerá el indicador de sesgo ** en el objeto de clase del objeto obj (tenga en cuenta que el indicador de apertura de bloqueo de sesgo en la clase, no el indicador de bloqueo de sesgo en el encabezado del objeto) ** está configurado para deshabilitar el sesgo cerrar. Las nuevas operaciones posteriores del objeto seguirán directamente la lógica de los bloqueos ligeros.
imagen.png

¿Están habilitados los bloqueos de polarización al comienzo del proceso?

Incluso si activa el bloqueo de polarización, hay un retraso en la activación del bloqueo de polarización, aproximadamente 4 segundos.

Es decir, dentro de los 4 segundos posteriores al inicio del proceso Java, el bloqueo sesgado se omitirá directamente y el bloqueo ligero se utilizará directamente cuando haya un bloque de código sincronizado.

La razón es que la sincronización se usa en muchos lugares en el código de inicialización de JVM. Si el sesgo se activa directamente, habrá una actualización de bloqueo en caso de competencia, lo que traerá una pérdida de rendimiento adicional. Después de las pruebas y la evaluación, el equipo de JVM eligió la solución de inicio más rápida. Es decir, el bloqueo de polarización se ve obligado a desactivarse en 4 segundos, por lo que existe esta estrategia de retraso (por supuesto, este tiempo de retraso también se puede ajustar mediante parámetros)

La importante historia de la evolución y el pensamiento del bloqueo sesgado

Los bloqueos sesgados se introdujeron en JDK6 y la optimización de bloqueo sesgado está habilitada de forma predeterminada. Los bloqueos sesgados se pueden deshabilitar a través del parámetro JVM -XX:-UseBiasedLocking.

Durante la evolución de jdk, muchas acciones, como la actualización por lotes y la revocación, como se mencionó anteriormente, se realizaron para el bloqueo de polarización.

Sin embargo, con el desarrollo de los tiempos, se encuentra que los costos de mantenimiento y revocación causados ​​por las cerraduras sesgadas son mucho mayores que las pocas acciones CAS de las cerraduras livianas.

Hay un párrafo de este tipo en la descripción oficial: dado que la introducción del bloqueo sesgado en HotSpot también cambió la cantidad de operaciones no disputadas necesarias para que esa relación siga siendo cierta.

Es decir, con el desarrollo del hardware, el costo de las instrucciones atómicas cambia, lo que da como resultado que se requieran menos instrucciones atómicas para bloqueos giratorios livianos (o menos operaciones cas para comprensión personal), por lo que el costo de los bloqueos giratorios disminuye, por lo que las ventajas que brindan los bloqueos están sesgados, incluso más pequeños .

Entonces, el equipo de jdk cerró el bloqueo de sesgo de forma predeterminada nuevamente después de Jdk15 .

Tal vez te preguntes, de que sirve aprender tanto antes, y no es recomendable usarlo.

Sin embargo, la mayoría de las aplicaciones de Java todavía se desarrollan en base a jdk8, y aún vale la pena aprender la idea de sesgar en el bloqueo.

También está el principio de la navaja de Occam: si el contenido agregado cuesta mucho, es mejor eliminarlo con valentía, aceptar una pequeña brecha y enfocarse en el lugar donde la mejora es mayor.

Cerradura ligera

La palabra clave del candado ligero se muestra a continuación. Puede ver que, excepto el bit indicador de estado del candado, todo lo demás se convierte en la dirección registrada por lockRecord en un marco de pila.
imagen.png

¿Adónde se fue la información en el markword original?

Como se mencionó anteriormente, hay atributos inherentes como edad generacional, cms_free y hashcode en markword.

Esta información se almacenará en lockRecord en el marco de pila de subprocesos correspondiente.

El formato de lockRecord y el proceso de almacenamiento/intercambio son los siguientes:
imagen.png

Además, tenga en cuenta que cuando el candado ligero no está bloqueado, la palabra clave en el encabezado del objeto almacena el contenido de la palabra clave y no se convierte en un puntero, solo se convertirá en un puntero durante el proceso de bloqueo. 

Por lo tanto, la cerradura liviana tiene operaciones repetidas de bloqueo y desbloqueo (la cerradura sesgada solo tendrá acciones similares cuando se reemplace el hilo sesgado)

El proceso de desbloqueo es el mismo y el encabezado del objeto se reemplaza por CAS.

¿Cómo maneja la cerradura ligera la reentrada de roscas?

Para el mismo subproceso, si ingresa repetidamente al bloque de sincronización, admite la reentrada en términos de semántica de sincronización (es decir, el subproceso que contiene el bloqueo puede ingresar al área de bloqueo varias veces). Para bloqueos livianos, esta función debe implementarse.

Por lo tanto, el lockRecord del subproceso no es un único miembro, en realidad es una colección lockRecord que puede almacenar varios lockRecords .

Cada vez que el subproceso abandona el bloque de sincronización, lockRecord se reduce en 1 y la acción de desbloqueo no se realizará hasta que lockReocrd contenga un puntero.
imagen.png

Proceso de bloqueo de bloqueo ligero

De acuerdo con el CAS anterior y la reentrada, el proceso de bloqueo al ingresar al bloque de código sincronizado se puede obtener:

  1. Antes de ingresar al bloque sincronizado, verifique si la dirección lockRecord se ha almacenado y si la dirección es consistente con su hilo actual. Si se ha guardado y es consistente, significa que está en la operación de reentrada y la lógica de reentrada se usa para agregar lockRecord.

  2. Si no es reentrante, verifique si lockRecord está ocupado por otros subprocesos. Si está ocupado por otros subprocesos, girará y esperará, y actualizará el bloqueo de peso pesado después de que el giro exceda el límite.

  3. Si no es reentrante y no está ocupado por otros subprocesos, elimine la dirección del puntero almacenada en lockRecord y luego reemplácela con CAS con su propia marca.

  4. Si el reemplazo falla, intente girar a re-CAS, la cantidad de fallas alcanza el límite superior y la actualización también se realiza.
    imagen.png

Proceso de desbloqueo de bloqueo ligero

De acuerdo con el problema de reentrada anterior, el proceso de salida de la cerradura liviana se puede obtener de la siguiente manera:
imagen.png

¿El límite superior del número de giros tiene que ser 10?

La optimización de bloqueos de giro en JDK 6 introduce un giro adaptativo.

Adaptativo significa que el tiempo de giro ya no es fijo, sino que está determinado por el tiempo de giro anterior en la misma cerradura y el estado del propietario de la cerradura. Si en el mismo objeto de bloqueo, el spin-wait acaba de adquirir correctamente el bloqueo y el subproceso que mantiene el bloqueo se está ejecutando, la máquina virtual considerará que es probable que el giro vuelva a tener éxito y permitirá que continúe. Relativamente más largo, como una duración de 100 bucles ocupados.

Por otro lado, si el giro rara vez adquiere con éxito un bloqueo para un determinado bloqueo, será posible omitir el proceso de giro directamente al adquirir el bloqueo en el futuro para evitar desperdiciar recursos del procesador. Con giro adaptativo, con el aumento del tiempo de ejecución del programa y la mejora continua de la información de monitoreo del rendimiento, la predicción del estado de bloqueo del programa de la máquina virtual será cada vez más precisa y la máquina virtual se volverá cada vez más "inteligente".

cerradura de peso pesado

Las cerraduras pesadas son las siguientes:

Cada objeto será generado por un objeto C++ de objectMonitor, que se apunta entre sí a través de la dirección, y C++ realiza la lógica subyacente.
imagen.png

Condiciones para actualizar a una cerradura de peso pesado

  1. Condiciones para pasar de un candado ligero a un candado pesado: girar más de 10 veces o alcanzar el límite de giro adaptativo

  2. Condiciones para la actualización directa de bloqueo sin bloqueo/parcializado a bloqueo pesado: si se llama al método object.wait(), ¡se actualizará directamente a un bloqueo pesado!

    La segunda condición se pasa por alto fácilmente.

donde esta la marca

El markwod en el encabezado del objeto, similar al procesamiento en el candado ligero, se almacena en el campo de encabezado del objeto objectMonitor.

Diagrama esquemático de sincronización de bloqueo de peso pesado

El candado de peso pesado de cada objeto apunta a un único objetoMonitor

Este objeto está implementado en C++

Hay muchas cosas en él, y el contenido es muy complicado. La relación entre cxq, entryList y qmod es muy complicada. Aquí, solo se explicará brevemente una parte del proceso, y puede que no sea todo correcto o contenga todos los detalles.

Así que se me ocurrió una frase que creo que es muy buena para decir:

"En lugar de estudiar el objectMonitor implementado por C++, es mejor estudiar el principio de AQS implementado por java. Las ideas centrales de los dos son en su mayoría las mismas. El código fuente de AQS es más amigable para las personas de java en términos de lenguaje, lo cual le permite comprender mejor los subprocesos Varios procesos de cola, espera, despertar”

Pero este artículo habla de la palabra clave de sincronización después de todo, así que hablemos brevemente sobre el proceso de monitoreo:

  1. Cuando el subproceso llama a monitorEntry por primera vez y es un bloqueo pesado, primero ingresará a la cola cxq
    imagen.png

  2. Cuando se trata de una competencia frecuente de bloqueos y se requiere el bloqueo, debe ingresar a la cola de lista de entradas.
    imagen.png

  3. Si el subproceso puede competir por el puntero de onwer por CAS, significa que el bloque de código de sincronización se mantiene con éxito.Si el CAS no puede competir, el bloque está bloqueado.
    imagen.png

  4. Cuando monitorExit sale, el subproceso bloqueado por el bloque en la lista de entrada se activará y volverá a competir
    imagen.png

  5. Si se llama al método object.wait(), el subproceso principal entrará en la cola de espera (tenga en cuenta que el subproceso que no puede competir no entrará en el conjunto de espera, y el conjunto de espera solo atiende a los subprocesos activados por el método de espera())
    imagen.png

  6. Al llamar a object.notify() o notificar a Todos, los subprocesos en la cola de espera ingresarán a cxq o entryList según el modo qmod.
    imagen.png

Una breve versión del proceso es la siguiente:
imagen.png

Reflexiones sobre la palabra clave sincronizada

Finalmente terminé de escribir, digo algo más.

Como todos sabemos, con la actualización continua de jdk, el JUC oficial y los componentes de sincronización derivados se están volviendo cada vez más poderosos. En comparación con sync, sync tiene muy pocas funciones, pero la lógica detrás de esto es extremadamente complicada. La operación de apagado predeterminada fue realizado.

Entonces, ¿sigue siendo necesaria esta palabra clave?

En primer lugar, muchos códigos históricos y algunas implementaciones internas de código jdk seguirán dependiendo de esta palabra clave para la sincronización y no se pueden reemplazar con AQS.

Además, independientemente de la lógica compleja detrás de la actualización, Sync es definitivamente mucho más simple de usar que JUC. Cuando su escenario es muy simple, pero de hecho hay problemas de sincronización, usar Sync mejorará la eficiencia de mucho desarrollo.

 

Haga clic en Seguir para conocer las nuevas tecnologías de HUAWEI CLOUD por primera vez~

{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4526289/blog/5549618
Recomendado
Clasificación