Analyse du code source à compte à rebours - Countdown ()
L'article précédent a parlé du principe d'Await () dans Countdownlatch à partir du niveau du code source. Cet article parle de Countdown ().
public void Countdown () {// CountdownLatch sync.reaseshared (1);} ↓ public final booléen releaseshared (int arg) {// aqs if (tryreleSeshared (arg)) {doreSeshared (); Retour Vrai; } return false;} ↓ booléen protégé TryReleSeshared (INT releases) {//countDownlatch.Sync // Décrément Count; Signal en transition vers zéro pour (;;) {int c = getState (); if (c == 0) return false; int nextc = c-1; if (comparableDsetState (c, nextc)) renvoie NextC == 0; }}Via le constructeur CountdownLatch end = new CountdownLatch (2); L'état est défini sur 2, donc c == 2, nextc = 2-1,
Réglez ensuite l'état sur 1 via l'opération CAS suivante.
Final Boolean comparentedSetState protégé (int attend, int update) {// Voir ci-dessous pour la configuration d'intrinsics pour prendre en charge ce retour dangeret.compareAndWapint (this, StateOffset, attendre, mise à jour); }À l'heure actuelle, NextC n'est pas 0 et revient faux. Attendez que la méthode compte à rebours () soit appelée deux fois, état == 0, nextc == 0, et renvoie true pour le moment.
Entrez la méthode DoreleSeshared ().
DoreleSeshared (); ↓ private void doreleSeshared () {/ * * Assurez-vous qu'une version se propage, même s'il y a d'autres * en cours acquièrent / relâche. Cela se déroule dans la manière habituelle * d'essayer de désactive la tête si elle a besoin de signal *. Mais si ce n'est pas le cas, le statut est défini pour se propager pour * s'assurer qu'à la libération, la propagation se poursuit. * De plus, nous devons boucler au cas où un nouveau nœud serait ajouté * pendant que nous faisons cela. De plus, contrairement à d'autres utilisations de * Unparksuccesseur, nous devons savoir si CAS à réinitialiser le statut * échoue, en cas de rechange. * / for (;;) {node h = tête; if (h! = null && h! = tail) {int ws = h.waitstatus; if (ws == node.signal) {if (! CompareAndSetWaitStatus (h, node.signal, 0)) continue; // Boucle pour revérifier les cas Unparksuccessor (H); } else if (ws == 0 &&! ComparandDsetWaitStatus (h, 0, node.propagate)) continue; // boucle sur cas échoué} if (h == tête) // boucle si la tête a changé de rupture; }}Rappelant le modèle de file d'attente d'attente pour le moment.
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Pour le moment, la tête n'est ni nulle ni que la queue. waitstatus == node.signal, alors entrez le jugement if (! ComparendSetWaitStatus (h, node.signal, 0)).
if (! CompareAndSetWaitStatus (h, node.signal, 0)) ↓ / ** * champ Waitstatus CAS d'un nœud. * / Private Static Final Boolean ComparandDsetWaitStatus (nœud nœud, int attend, int mette
Cette opération CAS définit l'état de 0, ce qui signifie que Waitstatus dans la tête est 0 pour le moment. Le modèle de file d'attente est le suivant
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Cette méthode renvoie true. Entrez Unparksuccessor (H);
Unparksuccessor (H); ↓ private void Unparksuccessor (nœud de nœud) {/ * * Si l'état est négatif (c'est-à-dire le signal de nécessité possible), essayez de dégager en prévision de la signalisation. C'est ok si cet * échoue ou si l'état est modifié par le thread d'attente. * / int ws = node.WaitStatus; if (ws <0) comparabledsetwaitStatus (nœud, ws, 0); / * * Le thread à Unstark est tenu dans le successeur, qui est normalement * juste le nœud suivant. Mais s'il est annulé ou apparemment nul, * traverse vers l'arrière de la queue pour trouver le successeur réel * non ennemis. * / Nœud s = node.next; if (s == null || s.WaitStatus> 0) {s = null; pour (nœud t = tail; t! = null && t! = node; t = t.prev) if (t.waitstatu <= 0) s = t; } if (s! = null) locksupport.unpark (s.thread);}S est le nœud successeur de la tête, c'est-à-dire le nœud avec le thread actuel. s! = null et s.waitstatus == 0, alors entrez locksupport.unpark (s.thread);
public static void uncark (thread thread) {if (thread! = null) unsetafe.unpark (thread); }Autrement dit, le fil qui se déverrouille bloqué. L'arbitre a été autorisé à dénoncer!
Le principe du compte à rebours () est très clair.
Chaque fois que le compte à rebours () est exécuté, l'état est réduit de 1. Jusqu'à l'état == 0, les threads bloqués dans la file d'attente commencent à être libérés et les threads des nœuds suivants sont libérés en fonction de l'état de Waitstatus dans le nœud prédécesseur.
Ok, revenez à la question de l'article précédent, quand la boucle suivante éclatera-t-elle (la boucle dans la méthode d'attente)
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, State == 0, alors entrez la méthode SetheadandPropagate.
SetheadandPropagate (Node, R); ↓ private void SetheadandPropagate (nœud nœud, int propagate) {nœud h = tête; // Enregistrez la vieille tête pour vérifier ci-dessous Sethead (nœud); / * * Essayez de signaler le nœud en file d'attente suivant si: * La propagation a été indiquée par l'appelant, * ou a été enregistrée (comme H.WaitStatus avant * ou après Sethead) par une opération précédente * (Remarque: Ceci utilise le signe de signalisation de Waitstatus parce que * propager le statut Des réveils inutiles, mais uniquement lorsqu'il y a plusieurs * Racing acquiert / sorties, donc la plupart ont besoin de signaux maintenant ou bientôt * de toute façon. * / if (propagate> 0 || h == null || h.waitstatu <0 || (h = head) == null || h.waitstatu <0) {node s = node.next; if (s == null || s.isshared ()) doreleSeshared (); }} ↓ private void sethead (nœud nœud) {head = node; Node.thread = null; node.prev = null;}Cette méthode modifie le nœud successeur de la tête dans la tête. Après cette méthode, le nœud de nœud suivant est défini sur null, et le modèle devient la figure suivante
crévuré + --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Autrement dit, la queue de tête de nœud et d'autres choses sont définies sur NULL, en attendant que GC recycle. À l'heure actuelle, retournez, sautez de la boucle pour et la file d'attente est effacée.
Voici une démonstration de l'ensemble du processus
SetheadandPropagate (Node, R); + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- thread = null | <---- nœud (queue) | CurrentThread | + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Node (queue) | CurrentThread | + ------------------------------- + ↓ + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Le noyau de CountdownLatch est une file d'attente de threads de blocage, qui est une file d'attente construite à partir d'une liste liée, qui contient du thread et de Waitstatus, où Waitstatus décrit l'état du thread du nœud successeur.
L'état est un drapeau très important. Lors de la construction, il est défini sur la valeur n correspondante. Si n! = 0, la file d'attente de blocage sera bloquée tout le temps à moins que le fil ne soit interrompu.
Chaque fois que la méthode à rebours () est appelée, l'état-1 est utilisé et la méthode Await () est utilisée pour ajouter le thread appelant la méthode à la file d'attente de blocage jusqu'à l'état == 0, et le thread ne peut pas être libéré.
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.