Compréhension des verrous en Java

Code de blocage simulé:

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();

    }

}

Lorsque le thread 1 a lock1, il essaie de posséder lock2 et le thread 2 a lock2 et essaie de posséder lock1, provoquant un blocage.

Serrure pessimiste

Les données adoptent une stratégie conservatrice pour la modification du monde extérieur

Il pense que les threads peuvent facilement modifier les données

Par conséquent, l'état verrouillé sera adopté pendant tout le processus de modification des données

Sachez qu'un thread est épuisé, d'autres threads peuvent continuer à utiliser

Code démo:

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


}

Compiler le code

// 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 verrouillé

MONITOREXIT déverrouille, libère des ressources

Verrou optimiste

Dans des circonstances normales, il n'y aura pas de conflit lorsque les données sont modifiées

Aucun verrou avant l'accès aux données

Les données ne seront testées que lorsque les données seront soumises pour modifications

Le verrouillage optimiste en Java est principalement obtenu par des opérations de comparaison et d'échange (CAS) de comparaison et d'échange. CAS est une instruction atomique de synchronisation multithread. L'opération CAS contient trois informations importantes, à savoir l'emplacement de la mémoire, la valeur d'origine attendue et la nouvelle valeur . Si la valeur de l'emplacement mémoire est égale à la valeur d'origine attendue, alors la valeur de l'emplacement peut être mise à jour avec la nouvelle valeur, sinon aucune modification n'est apportée.

CAS peut causer des problèmes ABA. La question 0 ABA fait référence au thread qui obtient la valeur attendue d'origine A, mais lorsque CAS est sur le point d'être exécuté, d'autres threads préemptent l'exécution à droite, en changeant cette valeur de A à B, puis d'autres threads changent cette valeur de B à A. Cependant, la valeur de A à ce moment n'est plus la valeur d'origine de A, mais le thread d'origine ne connaît pas cette situation. Lorsqu'il exécute CAS, il compare uniquement La valeur d'origine devrait être modifié, ce qui provoque le problème ABA.

Prenons l'exemple d'un drame policier. Si quelqu'un met une boîte de 100 W en espèces à la maison, il l'utilisera pour le racheter dans quelques minutes. Cependant, lorsqu'il ne fait pas attention, un voleur entre et échange une boîte vide contre Après avoir laissé la boîte pleine d'argent, quand quelqu'un entre et voit que la boîte est exactement la même, il pensera que c'est la boîte d'origine et la prendra pour échanger la personne. Il doit y avoir un problème dans cette situation car la boîte est déjà vide Oui, c'est le problème d'ABA.

La méthode de gestion courante d'ABA consiste à ajouter le numéro de version et à mettre à jour le numéro de version après chaque modification. Prenons l'exemple ci-dessus, si chaque fois que la boîte est déplacée, la position de la boîte changera et cette position de changement équivaut à " numéro de version ", quand quelqu'un entre et constate que l'emplacement de la boîte a changé, il sait que quelqu'un a bougé ses mains et ses pieds et abandonnera le plan d'origine, résolvant ainsi le problème ABA.

La classe AtomicStampedReference fournie par JDK 1.5 peut également résoudre le problème ABA. Cette classe gère un tampon "numéro de version". Chaque fois qu'elle compare non seulement la valeur actuelle mais également le numéro de version, elle résout le problème ABA.

Le verrouillage optimiste présente un avantage: il est verrouillé au moment de la soumission, il ne provoquera donc pas de blocage.

Serrure réentrante

Verrou récursif - fait référence au même thread, si la fonction externe possède le verrou, la fonction interne peut continuer à acquérir le verrou

Synchronized et ReentrantLock en Java sont des verrous réentrants

Démonstration du code de verrouillage réentrant:

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");
    }


}

On peut voir d'après les résultats que les threads d'exécution de la méthode reentrantA et de la méthode reentrantB sont tous les deux "main". Nous appelons la méthode reentrantA et sa méthode est imbriquée avec reentrantB. Si synchronized n'est pas réentrant, le thread sera bloqué tout le temps.

Le principe de réalisation des verrous réentrants est de stocker un identifiant de thread à l'intérieur du verrou pour déterminer à quel thread appartient le verrou actuel, et un compteur est maintenu à l'intérieur du verrou. Lorsque le verrou est inactif, la valeur de ce compteur est 0. Lorsque le verrou est le thread est occupé et réintégré, ajouter respectivement 1. Lorsque le verrou est libéré, le compteur est réduit de 1, jusqu'à ce qu'il soit réduit à 0, cela signifie que le verrou est inactif.

Serrure partagée et serrure pessimiste

Verrou exclusif signifie qu'au plus un thread peut maintenir le verrou à tout moment. Par exemple, synchronized est un verrou exclusif et ReadWriteLock verrou en lecture-écriture permet à plusieurs threads d'effectuer des opérations de lecture en même temps, ce qui est un verrou partagé.

Les verrous exclusifs peuvent être considérés comme des verrous pessimistes, et des verrous d'exclusion mutuelle doivent être ajoutés à chaque fois qu'une ressource est accédée, tandis que les verrous partagés peuvent être considérés comme des verrous optimistes, qui assouplissent les conditions de verrouillage et permettent à plusieurs threads d'accéder à la ressource en même temps. .

Je suppose que tu aimes

Origine blog.csdn.net/tangshuai96/article/details/111314542
conseillé
Classement