Comprensión de las tres características principales de JMM y uso simple de volátiles

concepto JMM

¿Qué son los JMM?
El nombre completo de JMM Java Memory Model, a saber, modelo de memoria Java, es un concepto lógico abstracto, solo un conjunto de convenciones o especificaciones.
Pensamiento: JMM存在的意义是什么?或者说它的作用是什么?
Estipula que en cualquier sistema operativo, la lectura y manipulación de datos entre subprocesos Java debe seguir este proceso, mostrando la relación abstracta entre subprocesos y memoria principal, para que los programas Java puedan lograr efectos de acceso consistentes en todas las plataformas.

Cada subproceso toma datos de la memoria principal y los copia en su propia memoria de trabajo (caché) para la operación.Si se trata de una operación de escritura, los datos modificados en su propia memoria de trabajo se escriben en la memoria después de la operación. Es decir, todas las operaciones de datos en la memoria de trabajo de cada subproceso son invisibles hasta que se escriben en la memoria principal.
inserte la descripción de la imagen aquí
El proceso general es el siguiente:
todas las variables definidas en el código que pueden ser compartidas por múltiples subprocesos se almacenan en la memoria principal, y cada subproceso tiene su propio espacio de memoria independiente (en el caché).Cuando un subproceso desea operar un determinado data , primero debe copiar una copia de estos datos de la memoria principal a su propio espacio de trabajo, y los datos del prototipo todavía están en la memoria. Después de que la CPU procesa los datos de caché en su propio espacio, se escriben desde el caché a la memoria. Nota: La CPU no puede leer ni escribir datos directamente de la memoria ( sin saltos ). El espacio de trabajo almacenado en caché por cada subproceso no está visible.

Reflexión: 为什么不直接操作内存?
debido a que existe una brecha entre la velocidad de la CPU y la velocidad de la memoria, si se leen todos los datos de la memoria, la mayor parte del tiempo de procesamiento de la CPU se desperdicia leyendo los datos. En este momento, se introduce un caché en la composición de la computadora. El caché es mucho más rápido que la memoria. Cuando opere datos, para mejorar la velocidad, primero lea los datos de la memoria al caché y luego lea el datos en el caché a la CPU, esto puede reducir la diferencia de velocidad entre la CPU y la memoria. Los datos en la memoria son visibles para cada subproceso, y los datos leídos por el subproceso actual desde la memoria al caché solo son visibles para el subproceso actual.

inserte la descripción de la imagen aquí
Pensando: Dado que el contenido de cada área de trabajo en un hilo es invisible entre sí, cómo comunicarse entre hilos. Por ejemplo: el subproceso A cambia la variable num de 0 a 1, y el subproceso B todavía lee la copia de 0, ¿cómo sabe B que A ha cambiado num a 1?

Memoria compartida que puede ser vista por cada subproceso. El subproceso A escribe num en la memoria compartida inmediatamente después de modificar num, mientras que el subproceso B no lee de la caché cuando opera la variable num, sino que lee los datos más recientes de la memoria a la caché. ¿Esto no hace que el subproceso B no use el contenido del caché cuando lee la variable num, sino que lo lea de la memoria nuevamente sin ralentizar la velocidad?
Para lograr una comunicación inmediata entre subprocesos para ciertas operaciones de datos, es necesario orientar estos datos De esta forma, si no es sensible a la puntualidad de la comunicación, también puede utilizar el caché para mejorar la velocidad.

Reflexionando: ¿Cuál es el hardware físico correspondiente a la memoria lógica compartida y el espacio de trabajo independiente del hilo?
La memoria compartida es la unidad de G en la computadora 内存条, y la mayor parte del caché de la CPU se refiere a la velocidad más rápida, pero la capacidad es pequeña y la unidad es M o K.寄存器

Tres características de JMM

Las tres características principales de JMM son tres cuestiones que deben considerarse en la programación concurrente.

  • La atomicidad
    es diferente de la atomicidad en las transacciones de MySQL. En MySQL, una sección se considera como un todo, ya sea que todas tengan éxito o todas fallen. En JMM, se centra en una operación atómica y ningún otro subproceso puede entrar en la operación. 更像是隔离性.
    La JVM puede compilar una línea de código en nuestro código en varias instrucciones de ensamblaje y luego ejecutarla. Por ejemplo: dos subprocesos A y B modifican num=0 en la memoria compartida a 1 en sus respectivos espacios de subprocesos, asumiendo el proceso 1: Copiar num de la memoria compartida en una copia en el espacio de memoria Proceso 2: Copiar num en el espacio de memoria Modifique el valor de num a 1, proceso 3: coloque el num modificado en la memoria principal.
    Suponga que el subproceso A comienza primero y ejecuta 1 y 2. En este momento, si no hay atomicidad, el sistema operativo suspende el subproceso A o el subproceso B interrumpe (la suspensión la determina el sistema operativo y el tiempo no se puede determinar). intervenido por sí mismo), y el subproceso B estará en una respiración Se ejecutan todos los procesos 1, 2 y 3. En este momento, el valor del número de memoria compartida ya es 1. En este momento, A se despierta y su proceso 3 se ejecuta, lo que equivale a reescribir num=1 nuevamente. Dado que los subprocesos no tienen atomicidad al asignar valores a las variables (creo que es más como un aislamiento), otros subprocesos se entrometen en la sección de lectura de datos, manipulación de datos y escritura de datos, lo que eventualmente hace que dos subprocesos operen num juntos , y finalmente hace num=1, no 2. Por lo tanto, la atomicidad de JMM debe considerarse en un entorno de subprocesos múltiples.

  • Visibilidad
    Múltiples subprocesos tienen la misma variable en su propio espacio de memoria.Si algún subproceso modifica esta variable, todos los demás subprocesos lo percibirán inmediatamente.
    Debido a JMM, cada subproceso primero copia una variable en su propio espacio de memoria, pero el espacio de memoria de cada subproceso es independiente y no se comparte. Si uno de los subprocesos modifica esta variable, otros subprocesos no lo percibirán hasta que el espacio de memoria de este subproceso se actualice en la memoria y vean los datos que se copiaron originalmente de la memoria principal.

  • Orden El
    orden se debe a que la JVM optimiza nuestro propio código y ajusta el orden de algunos códigos para que pueda lograr resultados más rápidos en tiempo de ejecución. Al igual que durante un examen, hay preguntas dispuestas en orden en el examen y, por lo general, elegimos hacer lo que sabemos, lo que es más eficiente. El orden de las preguntas en el examen es como el orden en que escribimos el código, y el orden de las preguntas reales es como el orden en que se compila el código. Incluso el reordenamiento no es un reordenamiento arbitrario, debe tener dependencias al frente. Por ejemplo: x=1, y=x; y depende de x, entonces x=1 definitivamente estará delante de y=x incluso después de la reorganización.
    Es bueno optimizar el orden y asegurar la consistencia final de los resultados, pero puede haber algunos problemas en la programación concurrente.Los
    siguientes son ejemplos de diferentes resultados si ocurre un reordenamiento de instrucciones:

public class Application {
    
    
    public static void main(String[] args) throws CloneNotSupportedException, InterruptedException {
    
    
        ReSortDemo reSortDemo = new ReSortDemo();
        new Thread(()->{
    
    reSortDemo.t1();},"A").start();
        new Thread(()->{
    
    reSortDemo.t2();},"B").start();
    }
}
class ReSortDemo{
    
    
    int num=0;
    boolean flag=false;
    void t1(){
    
    
        num=999; //语句1
        Thread.sleep(100);
        flag=false;  // 语句2
    }
    void t2(){
    
    
        while (!flag){
    
    
        }
        num=100;
        System.out.println(num);
    }

}

Si el subproceso A comienza primero, de acuerdo con el orden de ejecución de la instrucción 1 y la instrucción 2, cuando se ejecuta la instrucción 2, el subproceso B ingresa al método y el resultado del cálculo final num=100; pero tenga en cuenta: no hay dependencia entre la instrucción 1 y declaración 2, y puede suceder Las instrucciones se reorganizan para que la declaración 2 esté delante de la declaración 1, entonces el resultado final del cálculo es num=999; pero si está en un entorno de subproceso único, este problema no ocurrirá.

El uso de volátiles

Volatile es una palabra clave que puede proporcionar 轻量级un mecanismo de sincronización. Agregarlo delante de la variable puede garantizar la visibilidad de la variable durante la computación de subprocesos múltiples (cualquier subproceso que modifique su propio espacio de trabajo hará que otros subprocesos sean inmediatamente visibles) y prohibir la reorganización de instrucciones ( El la parte marcada no será optimizada por el compilador). Sin embargo Volatile不保证原子性, esto es diferente de synchronized、lockeste candado para trabajo pesado.

Visibilidad de volátil :

Verifique la invisibilidad entre varios subprocesos en JMM:
idea: prepare dos subprocesos (uno es un subproceso, el otro es un subproceso principal) y una clase de recurso. Después de que se inicie el subproceso A, copie los datos de la clase de recurso y modifíquelos, pero asegúrese de que el subproceso principal haya copiado los datos originales en su propio espacio de trabajo antes de modificar los datos (de lo contrario, el subproceso A puede escribir los datos en el subproceso principal). memoria muy rápidamente, hará que el hilo principal copie los datos modificados). Después de que A termina de modificar los datos, está evaluando si los datos obtenidos por el subproceso mian han cambiado. Por defecto, JMM será invisible, la siguiente es la verificación del código:
inserte la descripción de la imagen aquí

Pensando: ¿Por qué el subproceso A duerme por un tiempo antes de operar los datos?
Para garantizar que cuando el subproceso principal obtenga los datos, debe ser que A no se haya modificado. Solo de esta manera se puede verificar el modelo JMM, si el hilo A se modifica y se coloca en la memoria principal, el hilo mian irá a la memoria para copiar, de modo que el hilo haya completado la copia de datos a través de la memoria principal, y la invisibilidad de los datos no se puede verificar.

Verifique la visibilidad de Volatile:
inserte la descripción de la imagen aquí
Pensando: ¿Cómo es el proceso subyacente?
Dado que la palabra clave volatile se agrega a la variable num, después de que el subproceso A copie los datos de la memoria principal a su propio espacio de trabajo y los modifique, se escribirán en la memoria principal de inmediato. Cuando el subproceso principal adquiere esta variable, no lee los datos antiguos de su propio espacio de trabajo, sino que lee los datos más recientes de la memoria principal en su propio intervalo de espacio. Aunque las áreas de trabajo entre hilos son invisibles entre sí, los hilos pueden comunicarse instantáneamente a través de la memoria principal.
Pensando: ¿Disminuye el rendimiento en comparación con no agregar Volatile?
Después de agregar Volatile, los subprocesos necesitan obtener con frecuencia los datos más recientes de la memoria principal, y el caché del espacio de trabajo para esta variable no es válido. El uso del área de trabajo es para mejorar la velocidad, por lo que reducirá el rendimiento.

Pensando: Si no se usa Volatile, ¿se puede solucionar sincronizado?
La idea de usar sincronizados para resolver la visibilidad de variables: si varios subprocesos operan en la misma variable, los bloqueos de objetos sincronizados se agregan al método de modificación de datos y al método de obtención de datos. De esta manera, cuando el subproceso A opera num, toma el bloqueo de objeto de esta clase de recurso. El subproceso principal no tiene bloqueo de objeto de clase de recurso y no puede llamar al método getNum(). El subproceso principal solo puede adquirir el bloqueo y llamar al método getNum() después de que el subproceso A haya terminado de modificar y liberar el bloqueo (la variable se ha escrito desde el espacio de trabajo a la memoria principal cuando se libera el bloqueo).
inserte la descripción de la imagen aquí
Pero usar sincronizado para bloquear el método hace que el subproceso se ejecute en serie de nuevo demasiado engorroso.

Verificar que los volátiles no alcancen la atomicidad

  1. La idea de no agregar Volatile
    : configurar múltiples subprocesos para operar la misma variable, si la atomicidad no está garantizada, puede ocurrir una sobrescritura de valor.
    inserte la descripción de la imagen aquí

Pensando: 是什么原因造成的?
es porque num no es atómico en el proceso de buscar, asignar y devolver el valor.Hay otros procesos de subprocesos que entran entre buscar y escribir el valor. Por ejemplo: el subproceso A acaba de terminar de obtener el valor, el subproceso B ingresa, se ejecuta de una sola vez y A continúa ejecutándose, lo que da como resultado el mismo valor y la misma operación para obtener el mismo resultado. No se logra el efecto de la suma, pero se produce una anulación del valor del mismo valor.
Reflexión: countDownLatch在这里的作用是什么?
debido a que el resultado final se genera en el subproceso principal, lo que puede suceder es que el subproceso principal imprima el resultado antes de que cada subproceso haya terminado de calcular, lo que hace que el resultado sea más pequeño. Después de agregar countDownLatch, especifique la cantidad de subprocesos durante la inicialización, y al final cada subproceso se reducirá en 1. Si no se reduce a 0, el subproceso principal esperará. Muestra el resultado hasta que se ejecuten todas las ciudades nuevas.
2. Agregar volátil
inserte la descripción de la imagen aquí

Pensamiento: 如何理解这里的原子性?
aunque solo hay una línea de código para la asignación de variables, puede haber varias líneas de código en el momento de la compilación, lo que hace que la JVM tenga varios pasos durante la ejecución. Si estos pasos no se pueden completar de una sola vez y otros subprocesos se rompen, la atomicidad dentro de esta variable se destruirá. Atomicidad es tratar una sección como un todo e indivisible.

Pensamiento: volatile的可见性在这里发挥怎样的作用?
puede garantizar que después de que un subproceso modifique num, otros subprocesos puedan obtener inmediatamente el último valor modificado al leer. Otros, pero durante la operación de escritura, los datos de la memoria anterior se copian en los datos de su propio espacio de trabajo, que no se pueden leer y es posible que no sean los datos más recientes al escribir. Por lo tanto, la visibilidad de los volátiles no tiene efecto sobre la atomicidad.
Pensamiento: 那如果数据在写的时候,再次进行读取并与预期值比较,比较符合预期值后再进行修改(判断与修改保持原子性)这样是否就能达到整体的原子性?
Este es CASel principio fundamental. Todavía en el ejemplo anterior, el subproceso A cambia el valor de num a 1 y luego cuelga antes de escribir en el futuro, y el valor de num sigue siendo 0 en este momento. El subproceso B interviene, lee el valor de num como 0, modifica num a 1 y luego lo vuelve a escribir. En este momento, num=1, en este momento, el subproceso A obtiene el derecho de ejecución, y el subproceso A todavía piensa que num debería ser 0 en este momento, pero es 1 en este momento. Si el subproceso A hace un juicio atómico nuevamente, leerá que num ya es 1, lo cual es inconsistente con las expectativas. Luego haga otro procesamiento. Similar a una forma de bloqueo optimista, resuelve el problema de la atomicidad en subprocesos múltiples.
Pensando: ¿Cómo modificar el código anterior para que sea atómico?

  1. Los bloqueos se pueden usar para cambiar a ejecución en serie al escribir num. El problema de la atomicidad es solo cuando se realiza la escritura de subprocesos múltiples. Si se cambia directamente a serial al escribir, el problema se resolverá directamente.
    El método de uso de bloqueos sincronizados:
    inserte la descripción de la imagen aquí
    el método de uso de bloqueos:
    inserte la descripción de la imagen aquí
  2. Usar CAS
    inserte la descripción de la imagen aquí
    CAS es más como un bloqueo optimista, mientras que sincronizado es más como un bloqueo pesimista. Por el contrario, usar CAS es más eficiente.

Supongo que te gusta

Origin blog.csdn.net/m0_52889702/article/details/128868906
Recomendado
Clasificación