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. .