LockSupport de l'entrée à la compréhension approfondie

Questions d'entretien courantes chez LockSupport

1. Pourquoi LockSupport est-il également une classe de base de base ? Le framework AQS s'appuie sur deux classes : Unsafe (fournissant les opérations CAS) et LockSupport (fournissant les opérations de stationnement/déparkage) 2. Écrivez park/unpark pour obtenir la synchronisation via wait/notify et LockSupport respectivement ?
3. LockSupport.park() libérera-t-il les ressources de verrouillage ? Qu'en est-il de Condition.await() ?
4. Quelles sont les différences entre Thread.sleep(), Object.wait(), Condition.await() et LockSupport. park()?
5 , Points clés : Que se passera-t-il si notify() est exécuté avant wait() ?
6. Que se passera-t-il si unpark() est exécuté avant park() ?

1. Qu'est-ce que LockSupport ?

LockSupport est la primitive de blocage de thread de base utilisée pour créer des verrous et d'autres classes utilitaires de synchronisation.
Le cœur du framework de verrouillage et de synchronisation Java, AQS : AbstractQueuedSynchronizer, implémente le blocage et le réveil des threads en appelant LockSupport.park() et LockSupport.unpark(). LockSupport est très similaire à un sémaphore binaire (une seule licence est disponible pour l'utilisation). Si la licence n'a pas été occupée, le thread actuel obtient la licence et continue son exécution ; si la licence est déjà occupée, le thread actuel se bloque, en attendant de obtenir la licence.

Propriétés de la classe LockSupport

public class LockSupport {
    
    
    // Hotspot implementation via intrinsics API
    private static final sun.misc.Unsafe UNSAFE;
    // 表示内存偏移地址
    private static final long parkBlockerOffset;
    // 表示内存偏移地址
    private static final long SEED;
    // 表示内存偏移地址
    private static final long PROBE;
    // 表示内存偏移地址
    private static final long SECONDARY;
    
    static {
    
    
        try {
    
    
            // 获取Unsafe实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            // 线程类类型
            Class<?> tk = Thread.class;
            // 获取Thread的parkBlocker字段的内存偏移地址
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            // 获取Thread的threadLocalRandomSeed字段的内存偏移地址
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            // 获取Thread的threadLocalRandomProbe字段的内存偏移地址
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            // 获取Thread的threadLocalRandomSecondarySeed字段的内存偏移地址
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) {
    
     throw new Error(ex); }
    }
}

constructeur de classe

// 私有构造函数,无法被实例化
private LockSupport() {
    
    }

2. Trois façons de faire attendre et réveiller les discussions

La définition de LockSupport a été brièvement présentée plus tôt. Ensuite, nous présentons les trois mécanismes de blocage et de réveil en Java et résumons leurs avantages et inconvénients.

Méthode 1 : utilisez la méthode wait() dans Object pour faire attendre le thread, utilisez la méthode notify() d'Object pour réveiller le thread, combinée avec synchronisé ; Méthode 2 : utilisez la méthode wait() de Condition dans le package JUC pour faire en sorte que le thread attende
. le thread attend et utilise la méthode signal() pour réveiller le thread ;
Méthode 3 : La classe LockSupport peut bloquer le thread actuel et réveiller le thread bloqué spécifié ;

Nous démontrons trois méthodes avec des exemples spécifiques

La méthode 1 utilise wait() et notify() :

public class ObjectWait {
    
    

    public static void main(String[] args) {
    
    
        Object o = new Object();


        Thread t = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    

                System.out.println("线程A被o.wait()阻塞前");
                synchronized(o){
    
    
                    try {
    
    
                        o.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }

                }
                System.out.println("线程A被线程B o.notify()唤醒");

            }
        },"A");

        t.start();


        try {
    
    
            Thread.sleep(100);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("线程B唤醒线程A");
                synchronized (o){
    
    
                    o.notify();
                }
            }
        },"B").start();
    }
}

résultat:

线程A被o.wait()阻塞前
线程B唤醒线程A
线程A被线程B o.notify()唤醒

Nous bloquons le thread A via o.wait(), puis réveillons le thread A en exécutant la méthode o.notify() dans le thread B.
Remarque : 1. Wait et notify doivent tous deux être dans un bloc de synchronisation ou une méthode de synchronisation, c'est-à-dire c'est-à-dire, utilisez Pour synchroniser, la classe de ressources est verrouillée et doit apparaître par paires. 2. Vous devez attendre avant d'être averti lorsque vous l'utilisez, sinon l'attente ne sera pas réveillée, ce qui entraînera le blocage du fil.
Je n'ai pas démontré ici que l'attente ne se réveillera pas si vous notifiez d'abord puis attendez. Vous pouvez le tester par vous-même.

Méthode 2 : verrouiller .condition

public class ConditionAwait {
    
    

    public static void main(String[] args) {
    
    

        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    

                System.out.println("线程A被condition.await()阻塞前");

                try {
    
    
                    lock.lock();
                    condition.await();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    lock.unlock();
                }
                System.out.println("线程A被线程B condition.signl()唤醒");
            }
        }, "A").start();


        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    



                try {
    
    
                    lock.lock();

                    System.out.println("线程B中使用condition.signal()唤醒线程A");
                    condition.signal();
                }catch (Exception e){
    
    


                }finally {
    
    
                    lock.unlock();
                }

            }
        }, "B").start();

    }
}

结果:
线程A被condition.await()阻塞前
线程B中使用condition.signal()唤醒线程A
线程A被线程B condition.signl()唤醒

Remarque : 1. Le thread en attente et se réveillant dans Condition doit d'abord obtenir le verrou.
2. Il faut d'abord attendre, puis signaler, et non l'inverse.

Troisième méthode, utilisez LockSupport

public class LockSupportDemo {
    
    

    public static void main(String[] args) {
    
    

        Thread t = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    

                System.out.println("线程A被LockSupport.park()阻塞");
                LockSupport.park();

                System.out.println("线程A被线程B LockSupport.unpark()唤醒");

            }
        },"A");

        t.start();
        
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("线程B唤醒线程A");
                // 唤醒指定线程t,也就是A
                LockSupport.unpark(t);
            }
        },"B").start();
    }
}

结果:
线程ALockSupport.park()阻塞
线程B唤醒线程A
线程A被线程B LockSupport.unpark()唤醒

Comme le montre ce qui précède, le blocage et le réveil des threads à l'aide de LockSupport peuvent être effectués n'importe où dans le thread, et le thread spécifié peut être réveillé via unpart(thread). L'utilisation de LockSupport comme classe d'outils réduit également le couplage du code.

Utilisezrupt() pour interrompre le blocage de park()

package CompleteFuture;

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
    
    

    public static void main(String[] args) {
    
    

        Thread t = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    

                System.out.println("before park");
                LockSupport.park();
                System.out.println("after park");

            }
        },"A");

        t.start();


       //确保 park()执行
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
//        System.out.println("线程t是否被阻塞: "+t.isInterrupted());
        System.out.println("before interrupted");
        t.interrupt();
        System.out.println("after interrupted");

    }
}

结果:
before park
before interrupted
after interrupted
after park



Résumé des trois méthodes

méthode Caractéristiques défaut
attendre/avertir Wait et notify doivent tous deux être dans un bloc synchronisé ou une méthode synchronisée, c'est-à-dire que synchronisé doit être utilisé pour verrouiller la classe de ressources et doit apparaître par paires. 2. Vous devez attendre avant d'être averti lorsque vous l'utilisez, sinon l'attente ne sera pas réveillée, ce qui entraînera le blocage du fil. Besoin d'utiliser synchronisé
condition Il est nécessaire de combiner le verrouillage et le déverrouillage pour réveiller avec précision le thread spécifié (l'exemple n'est pas illustré), vous pouvez l'étudier par vous-même Sa couche inférieure utilise toujours LockSupport.
Support de verrouillage Utilisez park et unpark pour réveiller le thread spécifié. Peu importe si unpark ou park est exécuté en premier. Tant que les threads apparaissent par paires, ils seront libérés. Appeler unpark plusieurs fois ne peut le libérer qu'une seule fois.

3. Analyse du code source de LockSupport

Les méthodes dans LockSupport sont les suivantes :
Insérer la description de l'image ici

3.1Analyse du code source de park()

/**Disables the current thread for thread scheduling purposes unless the permit is available.
If the permit is available then it is consumed and the call returns immediately; otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens:
Some other thread invokes unpark with the current thread as the target; or
Some other thread interrupts the current thread; or
The call spuriously (that is, for no reason) returns.
This method does not report which of these caused the method to return. Callers should re-check the conditions which caused the thread to park in the first place. Callers may also determine, for example, the interrupt status of the thread upon return. 
*/
public static void park() {
    
    
        UNSAFE.park(false, 0L);
    }

Comment comprendre la méthode ci-dessus ?
S'il n'y a pas d'autorisation, après avoir appelé cette méthode, le thread actuel cessera immédiatement d'exécuter le plan (blocage) jusqu'à ce que l'une des trois situations suivantes se produise : 1. D'autres threads
appellent la méthode unpark (référencée par le thread bloqué), et le paramètre est le thread qui doit être réveillé
2. D'autres threads interrompent le thread en cours
3. L'appel renvoie faussement (c'est-à-dire sans raison) ;

La compréhension de UNSAFE.park(isAbsolute,timeout) bloque un thread jusqu'à ce que le déparquage apparaisse, et le thread

  • Interrompu ou délai expiré. Si un appel de reprise a eu lieu,

  • Je ne compte qu'ici. Un délai d'attente de 0 signifie qu'il n'expirera jamais. Lorsque isAbsolute est vrai,

  • le délai d'attente est en millisecondes par rapport à la nouvelle époque. Sinon, cette valeur correspond au nombre de nanosecondes avant l'expiration du délai. Quand cette méthode est exécutée

  • Il peut également revenir de manière déraisonnable (sans raisons spécifiques).Comprenez
    profondément le principe sun.misc.Unsafe.

3.2 déparquer(Thread thread)

 public static void unpark(Thread thread) {
    
    
        if (thread != null)
            UNSAFE.unpark(thread);
    }

Fournissez les informations d’identification de déblocage au fil spécifié. Si le thread spécifié utilise park(), le thread devient non bloquant. Si park n’est pas utilisé, le thread ne sera pas bloqué la prochaine fois qu’il utilisera park.

park(blocker) verrouille l'objet spécifié

public static void park(Object blocker) {
    
    
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 设置Blocker
    setBlocker(t, blocker);
    // 获取许可
    UNSAFE.park(false, 0L);
    // 重新可运行后再此设置Blocker
    setBlocker(t, null);
}

Remarque : lorsque vous appelez la fonction park, obtenez d'abord le thread actuel, puis définissez le champ parkBlocker du thread actuel, c'est-à-dire appelez la fonction setBlocker, puis appelez la fonction park de la classe Unsafe, puis appelez la fonction setBlocker. Voici donc la question : pourquoi devons-nous appeler la fonction setBlocker deux fois dans cette fonction park ? La raison est en fait très simple. Lors de l'appel de la fonction park, le thread actuel définit d'abord le champ parkBlocker, puis appelle la fonction Unsafe park. . Après cela, le thread actuel Il est déjà bloqué, attendant que la fonction de déparquage du thread soit appelée, de sorte que la fonction setBlocker suivante ne peut pas s'exécuter. La fonction de déparquage est appelée. Une fois que le thread a obtenu l'autorisation, il peut continuer à s'exécuter et le le deuxième setBlocker est exécuté. Le champ parkBlocker du thread est défini sur null, complétant ainsi la logique de l'ensemble de la fonction park. S'il n'y a pas de deuxième setBlocker, alors park(Object blocker) n'est pas appelé plus tard, mais la fonction getBlocker est appelée directement et le bloqueur défini par le park(Object blocker) précédent est obtenu, ce qui est évidemment illogique. En bref, il faut s'assurer qu'une fois la fonction park(Object blocker) entière exécutée, le champ parkBlocker du thread revient à null. Par conséquent, la fonction setBlocker doit être appelée deux fois dans la fonction park(Object). La méthode setBlocker est la suivante.

5. Une compréhension plus profonde

5.1 La différence entre Thread.sleep() et Object.wait() Thread.sleep() ne libérera pas le verrou qu'il détient, tandis que Object.wait() libérera le verrou qu'il détient ;

  • Thread.sleep() doit passer dans le temps, et Object.wait() peut ou non le transmettre. S'il n'est pas transmis, cela signifie qu'il continuera à bloquer ;
  • Thread.sleep() se réveillera automatiquement lorsque le temps sera écoulé, puis poursuivra l'exécution ;
  • Object.wait() n'a pas de temps et nécessite qu'un autre thread se réveille en utilisant Object.notify();
  • Object.wait() a du temps. S'il n'est pas notifié, il se réveillera automatiquement lorsque le temps sera écoulé. À ce stade, il existe deux situations. L'une est que le verrou est acquis immédiatement et le thread continuera naturellement à exécuter;l'autre est que le verrou n'est pas acquis immédiatement.Le thread entre dans la file d'attente de synchronisation et attend d'acquérir le verrou;
  • En fait, la plus grande différence entre eux est que Thread.sleep() ne libérera pas les ressources de verrouillage, tandis que Object.wait() libérera les ressources de verrouillage.

5.2 La différence entre Object.wait() et Condition.await()

  • Les principes de Object.wait() et Condition.await() sont fondamentalement les mêmes. La différence est que la couche inférieure de Condition.await() appelle LockSupport.park() pour bloquer le thread actuel.
  • En fait, il fait deux choses avant de bloquer le thread actuel : l'une consiste à ajouter le thread actuel à la file d'attente des conditions, et l'autre consiste à libérer "complètement" le verrou, c'est-à-dire à changer la variable d'état à 0, puis appelez LockSupport. .park() bloque le thread actuel.

5.3 La différence entre Thread.sleep() et LockSupport.park()

  • LockSupport.park() a également plusieurs méthodes frères - parkNanos(), parkUtil(), etc. La méthode park() dont nous parlons ici fait collectivement référence à ce type de méthode.
  • Fonctionnellement parlant, les méthodes Thread.sleep() et LockSupport.park() sont similaires : elles bloquent toutes deux l'exécution du thread en cours et ne libéreront pas les ressources de verrouillage occupées par le thread en cours ;
  • Thread.sleep() ne peut pas être réveillé de l’extérieur, il ne peut se réveiller que tout seul ;
  • La méthode LockSupport.park() peut être réveillée par un autre thread appelant la méthode LockSupport.unpark() ;
  • La déclaration de la méthode Thread.sleep() renvoie InterruptedException, l'appelant doit donc intercepter cette exception ou la lancer à nouveau ;
  • La méthode LockSupport.park() n'a pas besoin d'intercepter les exceptions d'interruption ;
  • Thread.sleep() lui-même est une méthode native, la couche inférieure de LockSupport.park() est la méthode native d'Unsafe appelée ;

5.4 La différence entre Object.wait() et LockSupport.park(). Les deux bloqueront l'exécution du thread actuel. Quelle est la différence entre eux ?

  • Après l’analyse ci-dessus, je pense que vous devez être très clair, vraiment ?
  • La méthode Object.wait() doit être exécutée dans un bloc synchronisé ; LockSupport.park() peut être exécuté n'importe où ;
  • La méthode Object.wait() déclare qu'une exception d'interruption est levée et que l'appelant doit l'intercepter ou la relancer.
  • LockSupport.park() n'a pas besoin d'intercepter les exceptions d'interruption ;
  • Object.wait() n'a pas de délai d'attente et nécessite qu'un autre thread exécute notify() pour le réveiller, mais il ne continue pas nécessairement à exécuter le contenu suivant ;
  • LockSupport.park() n'a pas de délai d'attente et nécessite qu'un autre thread exécute unpark() pour le réveiller, et le contenu suivant continuera certainement à être exécuté ;
  • Le principe sous-jacent de park()/unpark() est un "sémaphore binaire". Vous pouvez le considérer comme un sémaphore avec une seule licence, sauf que ce sémaphore n'ajoutera pas de licences supplémentaires lorsque unpark() est exécuté de manière répétée. Il existe une seule licence au maximum.

5.5 Que se passe-t-il si notify() est exécuté avant wait() ?

  • Si le thread actuel n'est pas propriétaire du verrou de cet objet, mais appelle la méthode notify() ou wait() de l'objet, une exception IllegalMonitorStateException est levée ;
  • Si le thread actuel est propriétaire de ce verrou d'objet, wait() bloquera toujours car il n'y aura pas d'autre notify() pour le réveiller plus tard.

5.6 Que se passe-t-il si unpark() est exécuté avant park() ?

Le fil ne sera pas bloqué, ignorez directement park() et continuez à exécuter le contenu suivant.

LockSupport.park() libérera-t-il la ressource de verrouillage ?

Non, il est uniquement responsable du blocage du thread actuel, et la libération de la ressource de verrouillage est en fait implémentée dans la méthode wait() de Condition.
Article de référence

https://blog.csdn.net/u013851082/article/details/70242395

Je suppose que tu aimes

Origine blog.csdn.net/u010445301/article/details/125437214
conseillé
Classement