Acceso sincrónico a datos variables compartidos en java

¿Qué es la sincronización primero?
La sincronización es para asegurar la consistencia de los objetos en un entorno multiproceso Al mismo tiempo, cuando un hilo ingresa a un bloque de código o método sincronizado, se pueden ver todos los efectos de modificación del hilo anterior protegido por el mismo bloqueo.

El lenguaje java garantiza que leer o escribir una variable es atómico, excepto para leer y escribir datos largos y dobles ( ¿por qué no es atómico al leer y escribir datos largos y dobles , usando AtomicLong / Double para leer y escribir datos largos / dobles? Garantizar atomic Mientras aseguramos la visibilidad de la memoria ), cuando leemos o escribimos variables, nuestras operaciones no necesitan estar sincronizadas, podemos asegurar la atomicidad de la operación, pero debido a las limitaciones de JMM (modelo de memoria java), lo siguiente figure Diagrama esquemático de JMM: La
Inserte la descripción de la imagen aquí
imagen proviene de: https://juejin.im/post/5d5df4d7518825661a3c1dfe Cuando
cada hilo esté funcionando, copiará una variable de la memoria principal y la almacenará en su propia memoria de trabajo, por lo que un hilo tiene una variable No se garantiza que los cambios sean visibles para otro hilo. Por lo tanto, incluso cuando tanto las lecturas como las escrituras son atómicas, también debemos sincronizar para la visibilidad de la memoria en un entorno multiproceso.

  • Ejemplos de acceso a variables compartidas en multiproceso sin sincronización:

public class StopThread {
    
    
    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
    
    
        Thread backgroundThread = new Thread(() -> {
    
    
            int i = 0;
            while (!stopRequested) {
    
    
                i++;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

El código anterior no se detiene después de ejecutarse, porque no hay sincronización y la modificación de la variable por un hilo no se obtiene por otro hilo.

  • Ejemplo de uso sincronizado para sincronizar el acceso a variables compartidas
 public class StopThread {
    
    
    private static boolean stopRequested;

    private static synchronized void requestStop(){
    
    
        stopRequested=true;
    }

    private static synchronized boolean stopRequested(){
    
    
        return stopRequested;
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        Thread backgroundThread=new Thread(() -> {
    
    
            int i=0;
            while (!stopRequested()){
    
    
                i++;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

El código anterior se detuvo aproximadamente un segundo después de ejecutarse. Esto está en línea con nuestras expectativas,
pero usando sincronizados para la sincronización. ¿Por qué agregamos sincronizados a ambos métodos? La razón es que los dos siguientes sincronizados aseguran que la memoria sea visible De hecho, aquí solo usamos las reglas de comunicación sincronizada, porque las operaciones de lectura y escritura en el propio método Java garantizan que es atómico.

Dos regulaciones de Sincronizado garantizan la visibilidad de la memoria:

1. Antes de que se desbloquee el hilo, el último valor de la variable compartida debe actualizarse en la memoria principal;
2. Cuando el hilo está bloqueado, el valor de la variable compartida en la memoria de trabajo se borra , de modo que el uso de shared Las variables requieren volver a leer el último valor de la memoria principal El valor de (el bloqueo y el desbloqueo requieren un bloqueo unificado)

El proceso de ejecución del subproceso del código mutex:
1. Obtener el mutex
2. Limpiar la memoria de trabajo
3. Copiar la última copia de variable de la memoria principal a la memoria de trabajo
4. Ejecutar el bloque de código
5. Vaciar el valor de variable compartida cambiado a la principal
6. Suelte el bloqueo mutex en la memoria

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#incorrectlySync

Por lo tanto, si solo agregamos sincronizado a uno para la sincronización y
solo agregamos sincronizado a stopRequested (), no hay forma de garantizar que requesttStop () escriba el último valor en la memoria principal. Si no está escrito en la memoria principal, entonces No hay forma de que stopRequested () obtenga el último valor.
Si solo agrega sincronizado a requestStop (), no hay forma de asegurarse de que el valor obtenido por stopRequested () sea el más reciente.

Si solo necesitamos lograr el efecto de la comunicación entre subprocesos, podemos usar la palabra clave volatile para asegurar la visibilidad de la memoria, de modo que cuando un subproceso lea la variable, obtenga el último valor de la variable.

  • Utilice volatile para sincronización y acceda a variables compartidas en un entorno multiproceso
public class StopThread {
    
    
    private static volatile boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
    
    
        Thread backgroundThread = new Thread(() -> {
    
    
            int i = 0;
            while (!stopRequested) {
    
    
                i++;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

El código anterior se detendrá automáticamente después de ejecutarse durante aproximadamente 1 segundo, logrando el efecto deseado

Pero al usar volátiles para la sincronización, debemos prestar atención al hecho de que la operación ++ de las variables volátiles modificadas no es atómica porque ++ se divide en dos pasos. Primero, lea el valor anterior y luego agregue uno. Si el segundo hilo está en el primero. Si un hilo lee la variable entre leer el valor anterior y escribir el nuevo valor, los dos hilos eventualmente obtendrán el mismo valor.

Entonces, ¿cómo evitar los problemas anteriores?
Utilice las herramientas del paquete Atomic.

Ejemplo: use AtomicLong para generar números de serie en un entorno multiproceso:

    private static AtomicInteger nextSerialNumber = new AtomicInteger();

    public static long generateSerialNumber() {
    
    
        return nextSerialNumber.getAndIncrement();
    }

El código anterior puede garantizar que obtengamos un número de serie único sin bloqueo.
Usamos la clase AtomicLong bajo el paquete java.util.concurrent.atomic. Esta clase asegura que la programación segura para subprocesos se pueda realizar en una sola variable sin bloquear, es decir, esta clase no solo garantiza el efecto de comunicación, sino también la atomicidad de operaciones está garantizada (el uso de AtomicLong / Double para leer y escribir long / double puede garantizar la atomicidad y garantizar la visibilidad de la memoria )

En resumen, es muy importante garantizar la sincronización de múltiples subprocesos cuando varios subprocesos comparten datos variables.

Lo anterior es mi propio aprendizaje y comprensión del acceso sincrónico de Java a datos variables compartidos. Si tiene algún error, corríjame. Bienvenidos a todos a comunicarse.

Supongo que te gusta

Origin blog.csdn.net/liu_12345_liu/article/details/103102888
Recomendado
Clasificación