Principio de programación concurrente de Java 1 (atomicidad, fila visible, orden, volátil, sincronizado)

1. Atomicidad:

1.1 ¿Cómo lograr la seguridad de subprocesos en Java?

Problemas con datos compartidos en operaciones multiproceso.
Cerrar con llave:

  • Bloqueo pesimista: sincronizado, bloqueo
  • Cerradura optimista: CAS

De acuerdo con la situación comercial, puede elegir ThreadLocal para permitir que cada subproceso juegue con sus propios datos.

1.2 Implementación subyacente de CAS

Desde la perspectiva de Java, CAS puede ver métodos nativos como máximo en el nivel de Java.
Sabrás comparar e intercambiar:

  • Primero compare si el valor es consistente con el valor esperado, si es consistente, intercambie y devuelva verdadero
  • Primero compare si el valor es consistente con el valor esperado, si no, no intercambie, devuelva falso

Puede ir a la operación CAS provista en la clase Insegura

Cuatro parámetros: qué objeto, el desplazamiento de memoria de qué atributo, oldValue, newValue

imagen.png

native es llamar directamente al método en la biblioteca de dependencia nativa C++.

https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/prims/unsafe.cpp

imagen.png

https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp

En la parte inferior de CAS, si se trata de un sistema operativo multinúcleo, se debe agregar una instrucción de bloqueo

No es necesario agregar un solo núcleo, porque cmpxchg es una instrucción de una línea y ya no se puede dividir

imagen.png

Al ver que cmpxchg es una instrucción de ensamblaje, el hardware de la CPU subyacente admite la comparación y el intercambio (cmpxchg), y cmpxchg no garantiza la atomicidad. (La operación de cmpxchg es una instrucción que no se puede dividir)

Es por eso que se evaluará si la CPU es multinúcleo y, si es multinúcleo, se agregará una instrucción de bloqueo.

Puede entender la instrucción de bloqueo como un bloqueo a nivel de CPU. Generalmente, la granularidad del bloqueo es un bloqueo a nivel de línea de caché . Por supuesto, también hay bloqueos de bus , pero el costo es demasiado alto y la CPU elegir de acuerdo a la situación.

1.3 CAS

ABA: ¡ ABA no tiene por qué ser el problema! Porque algunas operaciones que solo existen en ++, –, incluso si hay un problema ABA, ¡no afectará el resultado!

Subproceso A: espere cambiar el valor de A1 - B2

Subproceso B: Esperar valor de B2 - A3

Subproceso C: se espera que cambie el valor de A1 - C4

En términos de atomicidad, no se puede garantizar la seguridad del subproceso.

La solución es simple y ya se proporciona en el lado de Java.

imagen.png

En términos humanos, al modificar el valor, especifique el número de versión.

Se puede implementar la AtomicStampedReference proporcionada bajo JUC.

Demasiados giros:

¡Demasiados giros consumirán muchos recursos de la CPU! un desperdicio de recursos.

  • Dirección sincronizada: ¡Después de varias fallas de CAS, suspenda el subproceso (ESPERANDO) para evitar consumir demasiados recursos de CPU!
  • Dirección LongAdder: Aquí hay una solución basada en una forma similar a los bloqueos segmentados (dependiendo del negocio, hay restricciones). El AtmoicLong tradicional es a ++ para el único valor en la memoria. LongAdder ha creado muchos valores. en la .memoria

La atomicidad solo está garantizada para un atributo: la solución, puede comprenderla después de aprender AQS. ReentrantLock se implementa en base a AQS, y AQS implementa funciones básicas basadas en CAS.

1.4 Cuatro tipos de referencia + ThreadLocal

Cuatro tipos de referencia:

  • Referencia fuerte: Usuario xx = nuevo Usuario(); xx es una referencia fuerte, siempre que la referencia siga ahí, ¡GC no la reciclará!

  • Referencia blanda: el objeto al que hace referencia una SofeReference es una referencia blanda. Si el espacio de memoria es insuficiente, solo se reciclarán los puntos de referencia blanda al objeto. Generalmente se utiliza para el almacenamiento en caché .

    SoftwareReference xx = new SoftwareReference (new User);
    
    User user = xx.get();
    
  • Referencia débil: el objeto al que hace referencia WeakReference es generalmente una referencia débil. Siempre que se ejecute GC, solo se reciclará el objeto al que apunta la referencia débil. Puede resolver el problema de las fugas de memoria , solo mire ThreadLocal

  • Referencia fantasma: la función de PhantomReference es rastrear las actividades del recolector de basura para recolectar objetos.Durante el proceso de GC, si se encuentra una PhantomReference, el GC colocará la referencia en ReferenceQueue, que es manejada por el propio programador.Cuando el programador llama al método ReferenceQueue.pull (), después de eliminar el ReferenceQueue al que se hace referencia, el objeto Reference se volverá Inactivo, lo que significa que el objeto al que se hace referencia se puede reciclar.

En segundo lugar, la línea visible:

2.1 Modelo de memoria Java

Al procesar instrucciones, la CPU extraerá datos, la prioridad es de L1 a L2 a L3, si no hay ninguno, debe extraerse de la memoria principal, JMM debe coordinar entre la CPU y la memoria principal para garantizar la visibilidad y operaciones secuenciales efectivas.

¡No es la estructura de memoria de la JVM, nada! ! ! ! (Modelo de memoria Java)

imagen.png,
núcleo de la CPU, es el núcleo de la CPU (registro)

El caché es el caché de la CPU, y el caché de la CPU se divide en L1 (subproceso exclusivo), L2 (núcleo exclusivo), L3 (uso compartido de múltiples núcleos)

JMM es el núcleo del modelo de memoria de Java, y la visibilidad y el orden se basan en esta implementación.

La memoria principal JVM es su memoria de montón.

2.2 Formas de garantizar la visibilidad

Qué es la visibilidad: la visibilidad se refiere a si los cambios en las variables son visibles entre subprocesos

A nivel de Java, hay muchas formas de garantizar la visibilidad:

  • volatile , el uso del tipo de datos básico volátil puede garantizar que cada vez que la CPU opere datos, lea y escriba directamente en la memoria principal.
  • Sincronizado, la semántica de memoria de sincronizado puede garantizar que después de que se adquiera el bloqueo, se puede garantizar que los datos operados previamente sean visibles.
  • lock (CAS-volátil), también puede garantizar que después de CAS o la operación de variables volátiles, puede garantizar que los datos operados previamente sean visibles.
  • final, es una constante y no se puede mover~~

2.3 tipo de datos de referencia modificado volátil

Permítanme hablar sobre los resultados primero. En primer lugar, la modificación volátil del tipo de datos de referencia solo puede garantizar que la dirección del tipo de datos de referencia sea visible, y no garantiza que las propiedades internas sean visibles.

Pero, esta conclusión solo se puede realizar en el punto de acceso. Si cambia a una versión diferente de la máquina virtual, el efecto puede ser diferente. volatile modifica el tipo de datos de referencia, la JVM no ha estandarizado este tipo de operación en absoluto, y diferentes proveedores de máquinas virtuales pueden implementarlo por sí mismos.

2.4 Con el protocolo MESI, ¿por qué sigue habiendo volátiles?

MESI es un protocolo para coherencia de caché de CPU, y la mayoría de los fabricantes de CPU han logrado el efecto de coherencia de caché basado en MESI.

La CPU ya tiene el protocolo MESI, ¡no es volátil un poco redundante! ?

En primer lugar, los dos hermanos no entran en conflicto, uno es la consistencia a nivel de hardware de la CPU y el otro es la consistencia a nivel de JMM en Java.

El protocolo MESI tiene un mecanismo fijo, sin importar si declaras volátil o no, garantizará la consistencia (visibilidad) del caché basado en este mecanismo. Al mismo tiempo, debe quedar claro que si no hay protocolo MESI, volátil tendrá algunos problemas, pero hay otras soluciones (bloqueo de bus, el costo de tiempo es demasiado alto, si el bus está bloqueado, solo un núcleo de CPU es laboral).

MESI es un protocolo, un plan y una interfaz, y los fabricantes de CPU deben implementarlo.

Dado que la CPU tiene MESI, ¿por qué sigue siendo volátil? Naturalmente, hay un problema con el protocolo MESI. MESI garantiza la visibilidad entre las cachés exclusivas de las CPU multinúcleo, pero la CPU no significa que los datos de los registros deban escribirse directamente en L1, porque en la mayoría de las CPU con arquitectura x86, hay un almacenamiento intermedio entre los registros y L1, el valor del registro puede caer en el búfer de almacenamiento, pero no en L1, lo que generará inconsistencias en la memoria caché. Además de las CPU con arquitectura x86, las CPU en brazo y potencia, también hay búferes de carga y colas no válidas que afectarán más o menos la consistencia de la memoria caché.

El protocolo MESI y volátil no entran en conflicto, porque MESI está en el nivel de la CPU, y muchos fabricantes de CPU tienen implementaciones diferentes, y algunos detalles en la arquitectura de la CPU también lo afectarán. Por ejemplo, Store Buffer afectará la escritura de registros para la memoria caché L1, lo que genera incoherencias en la memoria caché. La capa inferior de volatile genera una instrucción de bloqueo de ensamblaje. Esta instrucción forzará a escribir en la memoria principal y puede ignorar la memoria caché del búfer de almacenamiento para lograr el propósito de visibilidad, y usará el protocolo MESI para invalidar otras líneas de memoria caché. *

2.5 Implementación subyacente de visibilidad volátil

La capa inferior de volatile genera una instrucción de bloqueo de ensamblado. Esta instrucción requerirá escritura forzada en la memoria principal, y el caché Store Buffer puede ignorarse para lograr el propósito de visibilidad, y el protocolo MESI se usará para invalidar otras líneas de caché.

3. Problemas ordenados de alta frecuencia:

3.1 ¿Qué es el problema del pedido?

Existe tal problema en el mecanismo perezoso en el modo singleton.

Para garantizar la seguridad de los subprocesos, las personas perezosas generalmente usan DCL.

Pero solo con DCL, todavía existe la posibilidad de problemas.

El subproceso puede hacer que funcionen objetos semiinicializados y es muy probable que se produzca NullPointException.

(Inicializar las tres partes del objeto, abrir espacio, inicializar propiedades internas y punteros a referencias)

Al compilar .java en .class en Java, la optimización se realizará en función de JIT y el orden de las instrucciones se ajustará para mejorar la eficiencia de ejecución.

A nivel de CPU, también se reordenan algunas ejecuciones para mejorar la eficiencia de ejecución.

El ajuste de esta instrucción causará problemas en algunas operaciones especiales.

3.2 La implementación subyacente del orden volátil

Los atributos modificados por volatile agregarán barreras de memoria antes y después de la compilación durante la compilación .

SS: las operaciones de lectura y escritura antes de la barrera deben completarse antes de realizar operaciones posteriores

SL: todas las operaciones de escritura antes de la barrera deben completarse antes de realizar operaciones de lectura posteriores

LL: las operaciones de lectura antes de la barrera deben completarse antes de realizar operaciones de lectura posteriores

LS: las operaciones de lectura antes de la barrera deben completarse antes de realizar operaciones de escritura posteriores

imagen.png

Esta barrera de memoria está especificada por el JDK, y el propósito es garantizar que los atributos modificados volátiles no tengan el problema de la reorganización de las instrucciones.

Volatile está en el nivel de JMM, es comprensible asegurarse de que JIT no se reorganice, pero ¿cómo lo implementa la CPU?

Consulte este documento: https://gee.cs.oswego.edu/dl/jmm/cookbook.html

imagen.png

Las diferentes CPU tienen cierto soporte para las barreras de memoria, por ejemplo, la arquitectura x86 ha implementado LS, LL y SS internamente, y solo admite SL.

Vaya a openJDK para comprobar de nuevo cómo lo admite mfence. De hecho, la capa inferior todavía está especificada por el bloqueo dentro de mfence para resolver el problema del reordenamiento de instrucciones.

imagen.png

四, sincronizado:

4.1 El proceso de actualización de bloqueo sincronizado

Un candado es un objeto, cualquiera está bien, todos los objetos en Java son candados.

Sin bloqueo (sesgo anónimo), bloqueo de sesgo, bloqueo ligero, bloqueo pesado

Sin bloqueo (sesgo anónimo) : en circunstancias normales, un objeto nuevo se encuentra en un estado sin bloqueo. Debido a que hay un retraso en el bloqueo sesgado, no hay ningún bloqueo sesgado en los 4 segundos de inicio de la JVM, pero si la configuración del retraso del bloqueo sesgado está desactivada, el nuevo objeto es un sesgo anónimo.

Bloqueo sesgado : cuando un subproceso determinado adquiere este recurso de bloqueo, se convertirá en un bloqueo sesgado en este momento, y el bloqueo sesgado almacena la ID del subproceso

Cuando se actualiza el bloqueo sesgado, activará la revocación del bloqueo sesgado. La revocación del bloqueo sesgado debe esperar hasta un punto seguro. Por ejemplo, durante GC, el costo de revocación del bloqueo sesgado es demasiado alto, por lo que por defecto, el bloqueo sesgado se retrasará al principio.

Punto seguro:

  • CG
  • antes de que el método regrese
  • después de llamar a un método
  • Posición anormal
  • final del ciclo

Candado ligero: cuando hay competencia entre varios subprocesos, es necesario actualizar a un candado ligero (es posible cambiar directamente de ningún candado a un candado ligero, y también es posible actualizar de un candado sesgado a un candado ligero ), El efecto de los bloqueos ligeros es intentar adquirir recursos de bloqueo basados ​​en CAS. Aquí se utilizan bloqueos de giro adaptativos. Según si el último CAS fue exitoso o no, se determina el número de giros esta vez.

Bloqueo de peso pesado: Si hay un bloqueo de peso pesado, no hay nada que decir, si hay hilos que sostienen el bloqueo, se suspenderán otros competidores.

4.2 Engrosamiento y eliminación de bloqueos sincronizados

Engrosamiento de bloqueo (expansión de bloqueo): (optimización JIT)

while(){
   sync(){
      // 多次的获取和释放,成本太高,优化为下面这种
   }
}
//----
sync(){
   while(){
       //  优化成这样
   }
}

Eliminación de bloqueo: en una sincronización, no hay recursos compartidos y no hay competencia de bloqueo. Cuando compila JIT, las instrucciones de bloqueo se optimizan directamente.

4.3 El principio de exclusión mutua sincronizada

Bloqueo sesgado: verifique el ID del hilo en MarkWord en el encabezado del objeto para ver si es el hilo actual. Si no, intente cambiarlo con CAS. Si es así, ha obtenido el recurso de bloqueo.

Bloqueo ligero: verifique si el puntero del registro de bloqueo en MarkWord en el encabezado del objeto apunta a la pila de la máquina virtual del hilo actual. Si es así, use el bloqueo para ejecutar el negocio. Si no es CAS, intente modificarlo. Modifique varias veces Si falla, inténtelo de nuevo Actualice a un candado de peso pesado.

Bloqueo pesado: Compruebe el ObjectMonitor al que apunta MarkWord en el encabezado del objeto para ver si el propietario es el subproceso actual. De lo contrario, tírelo a EntryList en ObjectMonitor, haga cola y suspenda el subproceso, a la espera de que se despierte.

imagen.png

4.4 ¿Por qué esperar es un método en Objeto?

Ejecutar el método de espera requiere mantener un bloqueo de sincronización.
El bloqueo de sincronización puede ser cualquier objeto.
Al mismo tiempo, se ejecuta el método de espera para liberar el recurso de bloqueo cuando se mantiene el bloqueo de sincronización.
En segundo lugar, el método de espera necesita operar ObjectMonitor, y la operación de ObjectMonitor debe operarse bajo la premisa de mantener el recurso de bloqueo, y el subproceso actual se arroja al grupo de espera de WaitSet.

De la misma manera, el método de notificación debe lanzar los subprocesos en el grupo de espera de WaitSet a EntryList. Si no posee ObjectMonitor, ¡cómo hacerlo!

Los bloqueos de clase se basan en clases. La clase se usa como bloqueo de clase.
El bloqueo de objeto es un nuevo objeto como bloqueo de objeto.

Supongo que te gusta

Origin blog.csdn.net/lx9876lx/article/details/129112793
Recomendado
Clasificación