CountdownLatch Source Code Analysis - Await (), le contenu spécifique est le suivant
L'article précédent a expliqué comment utiliser CountdownLatch. Cet article parlera du principe d'Await () du niveau du code source.
Nous savons déjà que vous attendre peut garder le fil actuel dans un état de blocage jusqu'à ce que le nombre de verres soit nul (ou interruption du thread).
Vous trouverez ci-dessous son code source.
end.Await (); ↓ public void attend () lance InterruptedException {sync.acquiresharedareterruply (1);}Sync est la classe intérieure de CountdownLatch. Voici sa définition.
Sync de classe finale statique privée étend AbstractQueEuedynchronizer {...}Il hérite de l'AbstractqueueEdSynchronizer. AbstractqueEuedSynchronizer Cette classe appartient à une classe très importante dans les fils Java.
Il fournit un framework pour implémenter des verrous de blocage et des synchroniseurs connexes (tels que des signaux, des événements, etc.) qui reposent sur les files d'attente d'attente FIFO.
Continuez et sautez à la classe AbstractqueueEdSynchronizer.
sync.ACQUIRESHAREDIGNABLIBLABILE (1); ↓ public final void acquireSharedInterruply (int arg) // abstractqueuedSynchronizer lève InterruptedException {if (thread.interrupted ()) lancez new interruptedException (); if (tryacQuireRared (arg) <0) doacquiresharedareterruply (arg);}Il y a deux jugements ici. Tout d'abord, déterminez si le fil est interrompu, puis portez le jugement suivant. Ici, nous regardons principalement le deuxième jugement.
protégé int tryacQuirsared (int acquier) {return (getState () == 0)? 1: -1;}Il convient de noter que la méthode TryAcQuirsared est implémentée en synchronisation.
Bien qu'il y ait des implémentations de celui-ci dans AbstractQueEuedSynchronizer, l'implémentation par défaut consiste à lancer une exception.
TryacquireShared Cette méthode est utilisée pour demander si l'état de l'objet actuel peut être autorisé à acquérir le verrou.
Nous pouvons voir qu'en synchronisation, nous renvoyons la valeur int correspondante en déterminant si l'état est 0.
Alors, que signifie l'état?
/ ** * l'état de synchronisation. * / État int privé volatile;
Le code ci-dessus montre clairement que l'état représente l'état de synchronisation.
Il convient de noter que l'état utilise le mot-clé volatil pour le modifier.
Le mot-clé volatil peut garantir immédiatement la modification de l'état à la mémoire principale. Lorsque d'autres threads doivent lire, la nouvelle valeur sera lue en mémoire.
C'est-à-dire que la visibilité de l'État est garantie. Il s'agit des dernières données.
Quel est l'état qui vient ici?
Ici, nous devons jeter un œil au constructeur de CountdownLatch.
CountdownLatch end = nouveau compte à rebours (2); ↓ public CountdownLatch (int count) {if (count <0) lance un nouveau IllégalArgumentException ("Count <0"); this.Sync = new sync (count);} ↓ sync (int count) {setState (count);}Il s'avère que les nombres du constructeur sont utilisés pour définir l'état.
Nous avons donc un état == 2 ici. TryAcquireshared Renvoie -1. Entrez ci-dessous
DoacquiresharedAredIntiblement (arg); ↓ private void doacquiresharedaredinterruptily (int arg) lance InterruptedException {nœud final nœud = addWaitter (node.shared); booléen a échoué = true; try {for (;;) {Node final p = node.predecessor (); if (p == Head) {int r = tryacQuireRared (arg); if (r> = 0) {SetheadAndPropagate (node, r); P.Next = null; // Aide GC Fails = false; retour; }} if (houstparkafterFailedacquire (p, node) && ParkAndCheckInterrupt ()) lancez new interruptedException (); }} enfin {if (échoué) CancelacQuire (node); }}Ok, ce code est un peu long et plusieurs fonctions y sont appelées. Regardons-le un par un.
Un nouveau nœud de classe apparaît dans la première ligne.
Le nœud est une classe interne dans la classe AQS (AbstractQueuedSynchronizer), qui définit une structure de chaîne. Comme indiqué ci-dessous.
+ ------ + PREV + ----- + + ----- + tête | | <---- | | <---- | | | queue + ----- + + ----- + + ----- +
N'oubliez pas cette structure.
Il existe également une méthode dans la première ligne de code addWaitter (Node.shared).
addWaitter (node.shared) //node.shared signifie que le nœud est en mode partagé ↓ nœud privé addWaitter (mode nœud) {nœud node = new nœud (thread.currentThread (), mode); // Essayez le chemin rapide de ENQ; Sauvegarde vers Full enq sur le nœud de défaillance Pred = Tail; // la queue de nœud volatile transitoire privé; if (pred! = null) {node.prev = pred; if (comparabledSettail (pred, node)) {pred.next = node; Node de retour; }} enq (nœud); Node de retour;}Tout d'abord, un nœud est construit et le thread actuel est stocké. Le mode est un mode partagé.
La queue signifie que la file d'attente de la file d'attente d'attente est nul en ce moment. Ainsi, pred == null entre enq (nœud);
enq (nœud) ↓ nœud privé enq (nœud nœud final) {pour (;;) {nœud t = tail; if (t == null) {// doit initialiser if (comparabledEthEad (new node ())) tail = head; } else {node.prev = t; if (comparabledSettail (t, node)) {t.next = node; retour t; }}}}La même queue est nulle, entrez Comparanddsethead.
CompareAndEthEad (nouveau nœud ()) ↓ / ** * champ de tête CAS. Utilisé uniquement par ENQ. * / Private Final Boolean CompareAndEtheDead (mise à jour du nœud) {return usAve.compareAndWapObject (This, Katingset, Null, Update);}Ceci est une opération CAS. Si la tête est nul, la tête de la file d'attente d'attente sera définie sur la valeur de mise à jour, qui est un nouveau nœud.
queue = tête; Ensuite, la queue n'est plus nul à ce moment. Entrez le cycle suivant.
Cette fois, le premier pointeur du nœud à la queue est d'abord le nœud, puis définissez le nœud sur la queue via une opération CAS et renvoyez la queue de la file d'attente, c'est-à-dire le nœud.
Le modèle de la file d'attente en attente change comme suit
+ ------ + PREV + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Ok, lorsque vous arrivez ici, la méthode Await revient, c'est un thread égal au nœud du thread actuel.
Retour à DoAcquiresharedAredIntily (int arg) et entrez la boucle suivante.
pour (;;) {Node final p = node.predecessor (); if (p == Head) {int r = tryacQuireRared (arg); if (r> = 0) {SetheadAndPropagate (node, r); P.Next = null; // Aide GC Fails = false; retour; }} if (houstparkafterFailedacquire (p, node) && ParkandCheckInterrupt ()) lancez new interruptedException ();}À l'heure actuelle, en supposant que l'état est encore supérieur à 0, puis R <0 pour le moment, alors entrez la méthode de ParkafterFailedacquire.
devraient-carkefterfailedacquire (p, nœud) ↓ booléen statique privé MoustarbarkafterFailedacquire (nœud pred, nœud nœud) {int ws = pred.waitStatus; if (ws == node.signal) // static final int signal = -1; / * * Ce nœud a déjà défini le statut demandant à une version * de le signaler, afin qu'il puisse se garer en toute sécurité. * / return true; if (ws> 0) {/ * * Le prédécesseur a été annulé. Ignorez les prédécesseurs et * RETRY INDUST. * / do {node.prev = pred = pred.Prev; } while (Pred.WaitStatus> 0); pred.next = node; } else {/ * * waitstatus doit être 0 ou se propager. Indiquez que nous avons * besoin d'un signal, mais ne nous garez pas encore. L'appelant devra * réessayer pour s'assurer qu'il ne peut pas acquérir avant le stationnement. * / CompareAndSetWaitStatus (Pred, Ws, Node.Signal); } return false;} ↓ / ** * CAS Waitstatus Champ d'un nœud. * / Private Static Final Boolean ComparandDsetWaitStatus (nœud nœud, int attend, int metteVous pouvez voir que le MaumdionafterFailedAcquire va également jusqu'à comparabledSetWaitStatus.
CompareAndSetWaitStatus définit le waignstatus de prev sur node.signal.
Node.Signal signifie que les threads des nœuds suivants doivent être sans étalage (similaires à être éveillés). Cette méthode renvoie false.
Après ce cycle, le modèle de file d'attente devient l'état suivant
+ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Étant donné que le coup de pied devrait redonner faux, nous ne examinerons plus les conditions suivantes. Continuez la boucle pour (;;);).
Si l'état est encore supérieur à 0, entrez à nouveau pour le ParkafterFailedacquire.
Cette fois, parce que Waitstatus dans la tête est Node.signal, MouflafterFailedAcquire renvoie vrai.
Cette fois, j'ai besoin de voir la méthode ParkandCheckInterrupt.
Final privé boolean ParkandCheckInterrupt () {lockSupport.park (this); return thread.interrupted (); }Ok, le fil n'est pas interrompu, donc, renvoie false. Continuez la boucle pour (;;);).
Si l'état est toujours supérieur à 0 et que le fil n'est pas interrompu, alors il est toujours dans cette boucle. Autrement dit, les arbitres ont mentionné dans l'article précédent qu'ils ont toujours été réticents à annoncer la fin du jeu.
Alors, dans quelles circonstances la boucle éclatera-t-elle? Autrement dit, dans quelles circonstances l'État sera-t-il inférieur à 0? J'expliquerai le prochain article.
Pour résumer, la méthode Await () consiste en fait à initialiser une file d'attente, à ajouter le thread qui doit être attendu (état> 0) à une file d'attente et à utiliser Waitstatus pour marquer l'état du thread du nœud successeur.
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.