Comprensión de los bloqueos en java

Código de interbloqueo simulado:

public class LockLearn {
    
    
    public static void main(String[] args) {
    
    
           deadlock();
    }
    private static void deadlock()
    {
    
    
        Object lock1=new Object();
        Object lock2=new Object();
        //线程1 拥有 lock1 试图获取lock2
        new Thread(()->{
    
    
            synchronized (lock1){
    
    
                System.out.println("获取 lock1 成功");
                try {
    
    
                    TimeUnit.SECONDS.sleep(3);
                }catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (lock2){
    
    
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();
        //线程2 拥有 lock2 试图获取lock1
        new Thread(()->{
    
    
            synchronized (lock2){
    
    
                System.out.println("获取 lock2 成功");
                try {
    
    
                    TimeUnit.SECONDS.sleep(3);
                }catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (lock1){
    
    
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();

    }

}

Cuando el subproceso 1 tiene lock1, intenta poseer lock2 y el subproceso 2 tiene lock2 e intenta poseer lock1, lo que provoca un interbloqueo.

Bloqueo pesimista

Los datos adoptan una estrategia conservadora para la modificación del mundo exterior.

Piensa que los hilos pueden modificar fácilmente los datos.

Por lo tanto, el estado bloqueado se adoptará durante todo el proceso de modificación de datos.

Sepa que un hilo está agotado, otros hilos pueden seguir utilizándose

Código de demostración:

public class LockLearn2 {
    
    
    public static void main(String[] args) {
    
    
        synchronized (LockLearn2.class)
        {
    
    
            System.out.println("这是一个悲观锁的演示");
        }
    }


}

Compilar código

// class version 52.0 (52)
// access flags 0x21
public class com/example/tangtang/boot/launch/JVM/LockLearn2 {
    
    

  // compiled from: LockLearn2.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 5 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/tangtang/boot/launch/JVM/LockLearn2; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
    // parameter  args
    TRYCATCHBLOCK L0 L1 L2 null
    TRYCATCHBLOCK L2 L3 L2 null
   L4
    LINENUMBER 7 L4
    LDC Lcom/example/tangtang/boot/launch/JVM/LockLearn2;.class
    DUP
    ASTORE 1
    MONITORENTER
   L0
    LINENUMBER 9 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\u8fd9\u662f\u4e00\u4e2a\u60b2\u89c2\u9501\u7684\u6f14\u793a"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 10 L5
    ALOAD 1
    MONITOREXIT
   L1
    GOTO L6
   L2
   FRAME FULL [[Ljava/lang/String; java/lang/Object] [java/lang/Throwable]
    ASTORE 2
    ALOAD 1
    MONITOREXIT
   L3
    ALOAD 2
    ATHROW
   L6
    LINENUMBER 11 L6
   FRAME CHOP 1
    RETURN
   L7
    LOCALVARIABLE args [Ljava/lang/String; L4 L7 0
    MAXSTACK = 2
    MAXLOCALS = 3
}

MONITORENTER está bloqueado

MONITOREXIT desbloquea, libera recursos

Cerradura optimista

En circunstancias normales, no habrá conflictos cuando se modifiquen los datos.

Sin bloqueos antes del acceso a los datos

Los datos se probarán solo cuando los datos se envíen para cambios

El bloqueo optimista en Java se logra principalmente mediante operaciones de intercambio y comparación de intercambio y comparación (CAS). CAS es una instrucción atómica de sincronización de múltiples subprocesos. La operación CAS contiene tres información importante, a saber, la ubicación de la memoria, el valor original esperado y el nuevo valor . Si el valor de la ubicación de la memoria es igual al valor original esperado, entonces el valor de la ubicación se puede actualizar al nuevo valor; de lo contrario, no se realiza ninguna modificación.

CAS puede causar problemas ABA. La pregunta 0 de ABA se refiere a que el subproceso obtiene el valor esperado original A, pero cuando CAS está a punto de llevarse a cabo, otros subprocesos se adelantan a la ejecución correcta, cambiando este valor de A a B, y luego otros subprocesos cambian este valor de B a A. Sin embargo, el valor de A en este momento ya no es el valor original de A, pero el hilo original no conoce esta situación. Cuando realiza CAS, solo compara El valor original se espera que sea modificado, lo que causa el problema ABA.

Tomemos como ejemplo un drama policial. Si alguien pone una caja de 100 W en efectivo en casa, se la llevará para canjear a alguien en unos minutos. Sin embargo, cuando no está prestando atención, entra un ladrón y cambia una caja vacía por Después de dejar la caja llena de dinero, cuando alguien entra y ve que la caja es exactamente la misma, pensará que esta es la caja original y la tomará para canjear a la persona. Debe haber un problema en esta situación porque la caja ya está vacía Sí, este es el problema de ABA.

El método de manejo común de ABA es agregar el número de versión y actualizar el número de versión después de cada modificación. Tome el ejemplo anterior, si cada vez que se mueve el cuadro, la posición del cuadro cambiará, y esta posición de cambio es equivalente a " Número de versión ", cuando alguien entra y encuentra que la ubicación de la caja ha cambiado, sabe que alguien ha movido sus manos y pies, y abandonará el plan original, resolviendo así el problema ABA.

La clase AtomicStampedReference proporcionada por JDK 1.5 también puede resolver el problema de ABA. Esta clase mantiene un sello de "número de versión". Cada vez que compara no solo el valor actual sino también el número de versión, resuelve el problema de ABA.

El bloqueo optimista tiene una ventaja: está bloqueado en el momento de la presentación, por lo que no provocará un punto muerto.

Bloqueo reentrante

Bloqueo recursivo: se refiere al mismo hilo, si la función externa es propietaria del bloqueo, la función interna puede continuar adquiriendo el bloqueo

Tanto sincronizados como ReentrantLock en Java son bloqueos reentrantes

Demostración del código de bloqueo reentrante:

public class LockLearn3 {
    
    
    public static void main(String[] args) {
    
    
        reentrantA();
    }

    private synchronized static void reentrantA() {
    
    
        System.out.println(Thread.currentThread().getName()+"执行reentrantA");
        reentrantB();
    }

    private synchronized static void reentrantB() {
    
    
        System.out.println(Thread.currentThread().getName()+"执行reentrantB");
    }


}

Se puede ver en los resultados que los subprocesos de ejecución del método reentrantA y el método reentrantB son ambos "principales". Llamamos al método reentrantA, y su método está anidado con reentrantB. Si sincronizado no es reentrante, el subproceso se bloqueará todo el tiempo.

El principio de realización de los bloqueos reentrantes es almacenar un identificador de hilo dentro del bloqueo para determinar a qué hilo pertenece el bloqueo actual, y se mantiene un contador dentro del bloqueo. Cuando el bloqueo está inactivo, el valor de este contador es 0. Cuando el bloqueo está inactivo, el valor de este contador es 0. el hilo está ocupado y reingresado, agregue respectivamente 1. Cuando se libera el candado, el contador se reduce en 1, hasta que se reduce a 0, significa que el candado está inactivo.

Cerradura compartida y cerradura pesimista

El bloqueo exclusivo significa que como máximo un subproceso puede mantener el bloqueo en cualquier momento. Por ejemplo, sincronizado es un bloqueo exclusivo, y el bloqueo de lectura y escritura ReadWriteLock permite que varios subprocesos realicen operaciones de lectura al mismo tiempo, que es un bloqueo compartido.

Los bloqueos exclusivos pueden entenderse como bloqueos pesimistas, y los bloqueos de exclusión mutua deben agregarse cada vez que se accede a un recurso, mientras que los bloqueos compartidos pueden entenderse como bloqueos optimistas, que relajan las condiciones de bloqueo y permiten que múltiples subprocesos accedan al recurso al mismo tiempo. .

Supongo que te gusta

Origin blog.csdn.net/tangshuai96/article/details/111314542
Recomendado
Clasificación