Java Multithreading-1-JMM

Uno, JMM

1. Concepto

JMM (modelo de memoria Java), modelo de memoria Java

La comunicación y la sincronización (entidades activas ejecutadas simultáneamente) entre subprocesos están todas controladas por JMM

Para la comunicación entre subprocesos de Java, JMM determina cuándo la escritura de un subproceso en una variable compartida es visible para otro subproceso

Sincronización entre subprocesos de Java, un mecanismo utilizado en el programa para controlar el orden relativo de operaciones entre diferentes subprocesos

La estructura principal de JMM incluye:

  1. Memoria principal (memoria compartida), utilizada para almacenar variables compartidas (almacenadas en el montón: instancias de objetos y matrices, almacenadas en el área de métodos: variables estáticas)
  2. Memoria local (memoria de trabajo), el área de memoria utilizada por cada hilo cuando se ejecuta, contiene una copia de las variables compartidas

2. Instrucciones de operación interactivas de memoria

JMM define 8 instrucciones de operación:

  1. leer y cargar

    read lee los datos en la memoria principal y los transmite a la memoria de trabajo

    load asigna el valor de la variable de lectura a una copia de la variable en la memoria de trabajo

  2. usar y asignar

    el uso pasa las variables en la memoria de trabajo al motor de ejecución

    asignar asigna el motor de ejecución a la variable

  3. almacenar 和 escribir

    La tienda transfiere los datos de la memoria de trabajo a la memoria principal.

    escribir almacena las variables transferidas de la tienda a la memoria principal

  4. bloquear 和 desbloquear

    el bloqueo identifica variables en la memoria principal como un estado exclusivo de subproceso

    desbloquear desbloquea las variables bloqueadas en la memoria principal

      read          load
主内存 ----> 工作内存 ----> 共享变量的副本

        use          assign
工作内存 ----> 执行引擎 ----> 共享变量的副本

            write         store
共享变量的副本 ----> 工作内存 ----> 主内存

      lock/unlock
主内存    ---->    共享变量

3. Visibilidad de la memoria

[1] El problema lleva a

private static int i = 0;

public static void main(String[] args) {
    
    
    new Thread(() -> {
    
    
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        i++;
        System.out.println("数值被改变为:" + i);
    }).start();

    while (0 == i) {
    
    
    }

    System.out.println("程序执行完毕");
}

Como puede ver, cuando el hilo recién creado imodifica las variables miembro , el programa aún se está ejecutando y no ha finalizado.

【2】 Causas y soluciones

Según el modelo JMM, hay 1 memoria principal y cada hilo tiene su propia memoria de trabajo . Cuando un hilo manipula datos, primero copiará los datos en su propia memoria de trabajo y luego los volverá a escribir en la memoria principal después de la modificación.

Las variables declaradas por defecto y las operaciones modificadas en la memoria de trabajo no serán conocidas por otros hilos

Si desea que esta operación sea vista por otros hilos, puede usar el modificador volátil para modificar la variable

A saber: volátil garantiza la visibilidad de la memoria

4. Atomicidad

[1] El problema lleva a

private static volatile int num = 0;

public static void main(String[] args) {
    
    
    int loop = 10;
    int count = 100;

    for (int i = 0; i < loop; i++) {
    
    
        new Thread(() -> {
    
    
            int number = addAndGet(count);
            System.out.println(Thread.currentThread().getName() + "获取数值:" + number);
        }, "线程A").start();
    }

    for (int j = 0; j < loop; j++) {
    
    
        new Thread(() -> {
    
    
            int number = addAndGet(count);
            System.out.println(Thread.currentThread().getName() + "获取数值:" + number);
        }, "线程B").start();
    }
}

private static int addAndGet(int count) {
    
    
    try {
    
    
        Thread.sleep(100);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    for (int i = 0; i < count; i++) {
    
    
        num++; // 复合操作
    }
    return num;
}

Se puede observar que numdespués de cada incremento , el resultado obtenido no es necesariamente countun múltiplo de. Incluso si se volatilemodifica, es inútil

【2】 Causas y soluciones

Cuando varios subprocesos ejecutan el mismo método, si no hay restricción, cada subproceso tiene la oportunidad de obtener el derecho de ejecución, lo que conduce a la modificación de datos en el método, que es ejecutado por varios subprocesos al mismo tiempo.

Por lo tanto, necesitamos agregar restricciones para asegurarnos de que solo haya un método de operación de subproceso al mismo tiempo

Esta restricción es para bloquear

Hay dos formas de lograrlo: usando sincronizado o java.util.concurrent.locks.Lock que aparece en JDK5

Es decir: sincronizado garantiza atomicidad, pero volátil no garantiza atomicidad

5. Reordenamiento de instrucciones

[1] El problema lleva a

/* 处理器A执行 */
a = 1;
x = b;

/* 处理器B执行 */
b = 2;
y = a;

/*
 * 执行结果
 * 初始状态:a = b = 0
 * 处理器允许执行后得到的结果:x = y = 0
 */

Si el procesador A y el procesador B ejecutan el acceso a la memoria en paralelo en la secuencia del programa, el x = y = 0resultado final posible

【2】 Causas y soluciones

Porque al realizar ayb de la asignación, el operador puede simplemente escribir datos en la zona del búfer de escritura en el interior, pero vaciarlos en la memoria principal de ellos. El búfer de escritura solo es visible para su propio procesador . Entonces, al leer los datos en la memoria principal, es posible leer datos sucios

Este fenómeno es el reordenamiento de las instrucciones. El orden en el que el procesador ejecuta las operaciones de memoria puede ser inconsistente con el orden real de las operaciones de memoria.

Todos los procesadores modernos usan búferes de escritura, por lo que todos permiten reordenar las operaciones de escritura y lectura.

Para mejorar el rendimiento, tanto el compilador como el procesador reordenan la secuencia de instrucciones

源代码 ---> 1[编译器] ---> 2[指令级] ---> 3[内存系统] ---> 最终执行的指令序列 
             优化重排序       并行重排序          重排序

Use volatile para modificar variables, puede prohibir el reordenamiento de instrucciones

Los métodos o bloques de código que usan sincronizados para sincronizar también pueden prohibir el reordenamiento de instrucciones

【3】 suceder antes

A partir de JDK5, Java comenzó a utilizar el nuevo modelo de memoria JSR-133

Happen-before se utiliza para ilustrar la visibilidad de la memoria entre operaciones

En JMM, si el resultado de la ejecución de una operación es visible para otra operación, existe una relación de suceder antes entre las dos operaciones. Estas dos operaciones pueden estar en el mismo hilo o en diferentes hilos

Nota: aquí es que el resultado de la ejecución de una operación es visible para otra operación, no que la operación anterior deba ejecutarse antes de la siguiente operación

Entender suceder antes es la clave para entender JMM

【4】 como en serie

No importa cómo reordene, el resultado de la ejecución del programa (de un solo subproceso) no se puede cambiar

P.ej:

int a = 5; // A
int b = 10; // B
int c = a + b; // C

No importa cómo reordene la secuencia de instrucciones, C debe ejecutarse en último lugar; de lo contrario, el resultado de la ejecución del programa cambiará. No hay dependencia de datos entre A y B , por lo que el compilador y el procesador pueden reordenar el orden de ejecución real entre A y B

Por lo tanto, en el caso de un solo hilo, no hay necesidad de preocuparse por la interferencia del reordenamiento de las instrucciones, y no es necesario considerar el impacto de la visibilidad de la memoria.

[5] Leer y escribir semántica de memoria de volátiles

Al escribir una variable volátil, JMM vaciará el valor de la copia de la variable compartida en la memoria de trabajo correspondiente al hilo en la memoria principal.

Al leer una variable volátil, JMM marcará la copia de la variable compartida en la memoria de trabajo correspondiente al hilo como no válida, y obtendrá el valor de la variable compartida de la memoria principal.

[6] Semántica de la memoria para adquirir y liberar bloqueos

Cuando un hilo adquiere un bloqueo, JMM marcará como inválida la copia de la variable compartida en la memoria de trabajo correspondiente al hilo, por lo que el código de la sección crítica protegida por el monitor debe obtener el valor de la variable compartida de la memoria principal

Cuando el hilo suelta el bloqueo, JMM vaciará el valor de la copia de la variable compartida en la memoria de trabajo correspondiente al hilo en la memoria principal.

Se puede ver que la semántica de la memoria de la escritura volátil y la semántica de la memoria de los bloqueos de liberación de subprocesos son la misma, y ​​la semántica de la memoria de la lectura volátil y la semántica de la memoria de los bloqueos de adquisición de subprocesos son las mismas.

6 、 final

Final proporciona garantías de seguridad para la inicialización : siempre que el objeto se construya correctamente (la referencia al objeto construido no se "escapa" en el constructor), entonces no es necesario utilizar la sincronización para garantizar que cualquier hilo pueda ver esto modificado por final El valor de la variable inicializada por el constructor (referencia al objeto modificado por final)

Escape de referencia: la referencia del objeto se mantiene en otros hilos antes de que el constructor regrese

interface EventSource {
    
    
    void registerListener(EventListener eventListener);
}

class Event {
    
     }

interface EventListener {
    
    
    void doEvent(Event e);
}

class ThisEscape {
    
    

    public ThisEscape(EventSource source) {
    
    
        source.registerListener(new EventListener() {
    
    
            @Override
            public void doEvent(Event e) {
    
    
                doSomething(e);
            }
        });
    }

    public void doSomething(Event e) {
    
    }

}

Cuando ThisEscape lanzó EventListener, también lanzó incondicionalmente una instancia que encapsuló ThisEscape. Porque la instancia de la clase interna contiene una referencia implícita a la instancia del paquete (esto)

Es decir, el orden de seguridad es:

  1. El constructor se ejecuta
  2. Objeto creado
  3. Apunta la referencia final del objeto modificado a la instancia del objeto

7. Resumen

Visibilidad Atomicidad Deshabilitar el reordenamiento de instrucciones
volátil
sincronizado
Bloquear
final Solo inicialización

Supongo que te gusta

Origin blog.csdn.net/adsl624153/article/details/103865132
Recomendado
Clasificación