Notas de programación concurrente 2_uso sincronizado y precauciones

Prefacio:

En el artículo anterior, aprendimos sobre las causas de los errores de concurrencia. Sabemos que cuando se agota el intervalo de tiempo de un subproceso, el sistema operativo cambiará a otro subproceso. Si estos dos subprocesos acceden a los mismos recursos, puede causar problemas de concurrencia. .
Podemos pensar que si este recurso compartido solo puede ser accedido por un hilo a la vez, y otros hilos no pueden acceder a él, no habrá problemas causados ​​por el cambio de hilos. La programación concurrente de Java proporciona un mecanismo de este tipo, bloqueos de exclusión mutua para garantizar que solo un hilo pueda acceder a los recursos compartidos a la vez.
Hay sincronizados y bloqueados en Java. Hoy veremos primero la palabra clave sincronizada.

Instrucciones:

Synchroized se divide en métodos de modificación y bloques de código:
métodos de modificación:

// 修饰方法  锁为对象
public synchronized void method1() {
    
    
       // 处理过程
   }

// 修饰静态方法 锁是类
public synchronized static void method2() {
    
    
       // 处理过程
   }

Bloque de código modificado:

// 修饰代码块 锁为对象
public void method3() {
    
    
      synchronized (this) {
    
    
          // 处理过程
      }
  }

// 修饰代码块 锁为类
  public void method4() {
    
    
      synchronized (Test.class) {
    
    
          // 处理过程
      }
  }

Veamos un ejemplo clásico de agregar una variable compartida a un hilo.

public class Test {
    
    

    private int i = 0;

    public void add() {
    
      // (1)
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        Test test = new Test();
        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 10000; i++) {
    
    
                test.add();
            }
        });
        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 10000; i++) {
    
    
                test.add();
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("i的值是" + test.i); // (2) i的值是18084
    }
}

El resultado esperado es 20000 y el valor calculado es menor que 20000. El subproceso t1 y el subproceso t2 se inician casi al mismo tiempo, y cada uno leerá el valor de i en su propia caché. Debido a que se agota un segmento de tiempo de subproceso, el sistema operativo cambiará Para otro subproceso,
el valor en la caché del subproceso t1 no se ha actualizado en la memoria en este momento, y el aumento del subproceso t2 a i es el valor en su propia caché, y luego, después del aumento de t2, el valor de i se vacía en la memoria. La operación de sumar t1 es igual a no hacerlo, por lo que el valor final de i será menor que 20000.

Cuando la palabra clave sincronizada se agrega en (1), se convierte en:
public sincronizada void add () {Cuando el hilo t1 ejecuta la función add (), primero ingresa al área crítica (es decir, la parte que es modificada por synchroized) y obtiene el monitor Bloqueo, si el cambio de hilo se realiza en este momento, otro hilo se bloqueará porque no ha obtenido el bloqueo, y cuando se agote el intervalo de tiempo. Cuando el subproceso se cambia a t1, puede continuar ejecutándose. Tal mutex que permite que solo funcione un hilo al mismo tiempo asegura que no habrá problemas con las variables compartidas.

Bloqueo reentrante

Reentrada: cuando un hilo adquiere el bloqueo, el hilo puede adquirir el bloqueo repetidamente sin bloquearse. Por ejemplo, en el siguiente ejemplo, si el subproceso 1 llama a add1 para adquirir el bloqueo, y luego adquiere el bloqueo después de llamar a add2, el subproceso 1 aún puede ejecutarse normalmente. Este es un bloqueo reentrante.
El otro bloqueo ReentrantLock de Java (el bloqueo reentrante también es El mismo efecto)

public synchronized void add1() {
    
    
              add()2;
          }

public synchronized void add2() {
    
    
            // 处理内容
        }

La realización de sincronizados

Nuestra siguiente clase de prueba se compila con javac Test.java, y el archivo de clase se descompila con javap -v Test para ver la información adicional

public class Test {
    
    
    public synchronized void method1(){
    
    
    }

    public void method2(){
    
    
        synchronized (Test.class){
    
    
        }
    }
}

Ver información

public synchronized void method1();
   descriptor: ()V
   flags: ACC_PUBLIC, ACC_SYNCHRONIZED
   Code:
     stack=0, locals=1, args_size=1
        0: return
     LineNumberTable:
       line 12: 0

 public void method2();
   descriptor: ()V
   flags: ACC_PUBLIC
   Code:
     stack=2, locals=3, args_size=1
        0: ldc           #2                  // class com/algorithm/leetcode/leetcode/Test
        2: dup
        3: astore_1
        4: monitorenter
        5: aload_1
        6: monitorexit
        7: goto          15
       10: astore_2
       11: aload_1
       12: monitorexit
       13: aload_2
       14: athrow
       15: return

Se puede ver que el método de sincronización en java es controlado por ACC_SYNCHRONIZED, y el bloque de código de sincronización es controlado por monitorenter y monitorexit Monitorenter significa ingresar a la zona crítica y monitorexit significa salir de la zona crítica.
sincronizado es controlado por el objeto monitor. Cualquier objeto java puede convertirse en un objeto monitor (puede entenderse simplemente como un bloqueo sincronizado), que almacena la información del hilo que posee el bloqueo. A partir de esta información, puede saber qué hilo contiene Hay este candado.
El objeto en el jvm se compone de variables de instancia, encabezados de objeto, datos de llenado, etc., y el monitor se almacena en el encabezado del objeto. El LockWord en el MarkWord del encabezado del objeto apunta a la dirección inicial del monitor, para que sepa qué hilo contiene bloquear.

Precauciones:

El siguiente es un tema del curso práctico de programación concurrente de Java:


class SafeCalc {
    
    
  long value = 0L;
  long get() {
    
    
    synchronized (new Object()) {
    
    
      return value;
    }
  }
  void addOne() {
    
    
    synchronized (new Object()) {
    
    
      value += 1;
    }
  }
}

Usar sincronizado de esta manera no surte efecto. Después de leer el contenido anterior, sabemos que la dirección del bloqueo está almacenada en el encabezado del objeto. Si dos objetos son nuevos, entonces hay dos bloqueos diferentes, por lo que no funcionarán.

Materiales de referencia:

1.https: //time.geekbang.org/column/intro/159 Práctica de programación simultánea en tiempo geek
2. "Práctica de programación simultánea en Java" Doug lea
3.http: //www.hollischuang.com/archives/1883 Sincronizado Principio de implementación
4.https: //docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10
5.https: //blog.csdn.net/sc9018181134/article / details / 80643360 (objeto klass en mi otro blog)

Supongo que te gusta

Origin blog.csdn.net/sc9018181134/article/details/103230413
Recomendado
Clasificación