Dans l'article précédent "Java Concurrency Series [1] ----- AbstractQueEuedSynchronizer Code Analysis", nous avons introduit certains concepts de base d'AbstractqueEuedSynchronizer, en parlant principalement de la façon dont le domaine de la file d'attente des AQS est implémenté, quel est le mode exclusif et le mode partagé, et comment comprendre l'état d'attente des nœuds. La compréhension et la maîtrise de ces contenus est la clé de la lecture ultérieure du code source AQS, il est donc recommandé aux lecteurs de lire mon article précédent en premier, puis de revenir sur cet article pour le comprendre. Dans cet article, nous présenterons comment les nœuds saisissent la file d'attente de file d'attente de synchronisation en mode exclusif et quelles opérations seront effectuées avant de quitter la file d'attente de synchronisation. AQS fournit trois façons d'obtenir des verrous en mode exclusif et en mode partagé: acquisition d'interruption de thread non réactive, acquisition d'interruption du thread de réponse et réglage de l'acquisition de temps mort. Les étapes globales de ces trois méthodes sont à peu près les mêmes, avec seulement quelques parties différentes, donc si vous comprenez une méthode et regardez la mise en œuvre d'autres méthodes, vous serez similaire. Dans cet article, je me concentrerai sur la méthode d'acquisition pour ne pas répondre aux interruptions du fil, et les deux autres méthodes parleront également des incohérences.
1. Comment obtenir des verrous avec des interruptions de fil non réactives?
// ne répond pas à l'acquisition de la méthode d'interruption (mode exclusif) public final void acquire (int arg) {if (! TryacQuire (arg) && acquireeUeUed (addWaitter (node.exclusive), arg)) {selfinterrupt (); }}Bien que le code ci-dessus semble simple, il effectue les 4 étapes illustrées dans la figure ci-dessous dans l'ordre. Ci-dessous, nous allons démontrer et analyser étape par étape.
Étape 1 :! Tryacquire (arg)
// Essayez d'acquérir le verrou (mode exclusif) Boolean Tryacquire (int arg) {lancez un nouveau UnsupporttedOperationException ();}À ce moment, quelqu'un est venu et il a essayé de frapper à la porte en premier. S'il constatait que la porte n'était pas verrouillée (tryacquire (arg) = vrai), il irait directement. Si vous constatez que la porte est verrouillée (tryacquire (arg) = false), effectuez l'étape suivante. Cette méthode TryAcquire détermine quand le verrouillage est ouvert et lorsque le verrou est fermé. Cette méthode doit être écrasée par les sous-classes et réécrire la logique de jugement à l'intérieur.
Étape 2: AddWaitter (Node.Exclusive)
// Enveloppez le thread actuel dans un nœud et ajoutez-le à la queue de la file d'attente de synchronisation nœud privé addWaitter (mode nœud) {// Spécifiez le mode tenant le nœud de verrouillage Node = nouveau nœud (thread.currentThread (), mode); // Obtenez la référence du nœud de queue du nœud de file d'attente de synchronisation Pred = Tail; // Si le nœud de queue n'est pas vide, cela signifie que la file d'attente de synchronisation a déjà un nœud if (pred! = Null) {// 1. Pointer vers le nœud de queue actuel Node.Prev = Pred; // 2. Définissez le nœud actuel sur le nœud de queue if (CompareAndSettail (pré, nœud)) {// 3. Pointer le successeur de l'ancien nœud de queue vers le nouveau nœud de queue pred.next = node; Node de retour; }} // Sinon, cela signifie que la file d'attente de synchronisation n'a pas été initialisée enq (nœud); Node de retour;} // nœud nœud nœud privé enq (nœud nœud final) {pour (;;) {// Obtenez la référence du nœud de queue du nœud de file d'attente de synchronisation t = tail; // Si le nœud de queue est vide, cela signifie que la file d'attente de synchronisation n'a pas été initialisée if (t == null) {// initialiser la file d'attente de synchronisation if (comparabledEthEad (new node ())) {tail = tête; }} else {// 1. Pointer vers le nœud de queue actuel node.prev = t; // 2. Définissez le nœud actuel sur le nœud de queue if (CompareAndSettail (t, nœud)) {// 3. Pointer le successeur de l'ancien nœud de queue vers le nouveau nœud de queue t.next = nœud; retour t; }}}}L'exécution de cette étape indique que la première fois que l'acquisition de verrouillage a échoué, donc la personne obtiendra une carte numérique pour lui-même et entrera dans la zone de file d'attente pour la file d'attente. Lors de la réception de la carte numéro, il déclarera comment il veut occuper la salle (mode exclusif ou mode de partage). Notez qu'il ne s'était pas assis et ne se reposait pas à ce moment (accrochez-vous).
Étape 3: AcquirequeUeUe (addWaitter (node.exclusive), arg)
// Acquérir le verrou de manière sans interruption (mode exclusif) final booléen acquirequeUeUed (nœud de nœud final, int arg) {booléen failli = true; essayez {booléen interrompu = false; pour (;;) {// Obtenez la référence du nœud précédent du nœud final du nœud donné p = node.predecessessor (); // Si le nœud actuel est le premier nœud de la file d'attente de synchronisation, essayez d'acquérir le verrou si (p == head && tryacquire (arg)) {// définit le nœud donné comme nœud de tête Sethead (nœud); // Pour aider à la collecte des ordures, effacez le successeur du nœud de tête précédent P.Next = NULL; // Définit l'état d'acquisition réussi Échec = false; // Renvoie l'état interrompu, toute la boucle est exécutée ici, le retour de sortie interrompu; } // Sinon, cela signifie que l'état de verrouillage n'est toujours pas disponible. À l'heure actuelle, déterminez si le thread actuel peut être suspendu // Si le résultat est vrai, le thread actuel peut être suspendu, sinon la boucle continuera, pendant cette période, le thread ne répondra pas à l'interruption if (HaudParkAfterFailedAcquire (p, node) && ParkandCheckInterrupt ()) {interrupted = true; }}} Enfin {// Assurez-vous d'annuler l'acquisition if (a échoué) {Cancelacquire (node); }}} // juger si elle peut suspendre le nœud actuel statique privé booléen devrait-ilpararkafterFailedacquire (nœud pred, nœud nœud) {// obtient l'état d'attente du nœud avant int ws = pred.waitsatus; // Si l'état du nœud avant est le signal, cela signifie que le nœud avant réveillera le nœud actuel, de sorte que le nœud actuel peut suspendre en toute sécurité if (ws == node.signal) {return true; } if (ws> 0) {// L'opération suivante consiste à nettoyer tous les nœuds avant annulés dans la file d'attente de synchronisation do {node.prev = pred = pred.prev; } while (Pred.WaitStatus> 0); pred.Next = node; } else {// À cette fin, cela signifie que l'état du nœud avant n'est pas un signal, et il est probablement égal à 0. De cette manière, le nœud avant ne réveillera pas le nœud actuel.//so le nœud actuel doit s'assurer que l'état du nœud avant est le signal pour le suspendre en toute sécurité. } return false;} // suspendre le thread actuel final privé final booléen ParkAndCheckInterrupt () {locksupport.park (this); return thread.interrupted ();}Après avoir obtenu le panneau numérique, il implémentera immédiatement cette méthode. Lorsqu'un nœud entre dans la zone de file d'attente pour la première fois, il y a deux situations. La première est qu'il constate que la personne devant lui a quitté son siège et est entrée dans la pièce, donc il ne s'asseoir pas et ne se reposera pas, et frappera à nouveau à la porte pour voir si l'enfant est terminé. Si la personne à l'intérieur était juste terminée, il se précipiterait sans s'appeler. Sinon, il devrait envisager de s'asseoir et de se reposer pendant un certain temps, mais il était toujours inquiet. Et si personne ne lui rappelait après s'être assis et s'est endormi? Il a laissé une petite note sur le siège de l'homme devant afin que la personne qui est sortie de l'intérieur puisse le réveiller après avoir vu la note. Une autre situation est que lorsqu'il est entré dans la file d'attente et a constaté qu'il y avait plusieurs personnes qui faisaient la queue devant lui, il pouvait s'asseoir pendant un certain temps, mais avant cela, il laissait toujours une note sur le siège de la personne devant (il dormait déjà à ce moment) afin que la personne puisse le réveiller avant de partir. Quand tout est fait, il dort paisiblement. Notez que nous voyons que l'ensemble de la boucle n'a qu'une seule sortie, c'est-à-dire qu'il ne peut s'éteindre qu'après que le thread ait réussi à acquérir la serrure. Avant l'obtention de la serrure, il est toujours suspendu dans la méthode ParkandCheckInterrupt () de la boucle pour. Une fois le fil éveillé, il continue également d'exécuter la boucle pour cet endroit.
Étape 4: Selfinterrupt ()
// Le thread actuel s'interrompra s'interrompent privé static void selfinterrupt () {thread.currentThread (). Interrupt (); }Étant donné que l'ensemble du fil ci-dessus a été suspendu dans la méthode ParkandCheckInterrupt () de la boucle FOR, il ne répond à aucune forme d'interruption de fil avant d'être acquise avec succès. Ce n'est que lorsque le fil acquiert avec succès le verrou et sort de la boucle pour vérifier si quelqu'un demande à interrompre le fil pendant cette période. Si c'est le cas, appelez la méthode Selfinterrupt () pour s'accrocher.
2. Comment obtenir des verrous en réponse aux interruptions de fil?
// Acquérir le verrouillage en mode interruptible (mode exclusif) private void doacquireInterruply (int arg) interrompre InterruptedException {// Envelopper le thread actuel dans un nœud et l'ajout à la file d'attente de synchronisation Node final Node = AddWaitter (Node.Exclusive); booléen a échoué = true; essayez {pour (;;) {// obtenant le nœud final du nœud précédent p = node.predecessor (); // Si P est un nœud de tête, le thread actuel essaie d'acquérir à nouveau le verrou si (p == head && tryacquire (arg)) {SetHead (nœud); P.Next = null; // Aide GC Fails = false; // Retour retour après l'acquisition avec succès; } // Si la condition est remplie, le thread actuel sera suspendu. À l'heure actuelle, une interruption est répondue et une exception est lancée si (MaudParkAfterFailedAcquire (p, node) && ParkandCheckInterrupt ()) {// Si le thread est éveillé, une exception sera lancée si la demande d'interruption est trouvée, un échec sera jeté. Jetez une nouvelle interruption (); }}} enfin {if (échoué) {Cancelacquire (node); }}}La méthode d'interruption du thread de réponse et la méthode d'interruption du thread non réactive sont à peu près les mêmes dans le processus d'obtention de verrous. La seule différence est qu'après que le fil se réveille de la méthode ParkandCheckInterrupt, il vérifiera si le fil est interrompu. Si c'est le cas, il lèvera une exception InterruptedException. Au lieu de répondre au verrouillage de l'acquisition d'interruption du thread, il ne définit que l'état d'interruption après avoir reçu la demande d'interruption et ne mettra pas immédiatement la méthode actuelle pour acquérir le verrou. Il ne décidera pas de s'accrocher en fonction de l'état d'interruption après que le nœud ait réussi à acquérir la serrure.
3. Comment définir le temps de temps pour acquérir la serrure?
// Acquérir le verrou avec un délai limité (mode exclusif) booléen privé doacquirerenanos (int arg, long nanostimeout) lève InterruptedException {// Obtenir le système actuel du système long dernier = System.NanoTime (); // Emballage du thread actuel dans un nœud et l'ajout à la file d'attente de synchronisation Node final Node = addWaitit (Node.Exclusive); booléen a échoué = true; essayez {pour (;;) {// obtenant le nœud final du nœud précédent p = node.predecessor (); // Si le nœud précédent est un nœud de tête, le thread actuel essaie d'acquérir à nouveau le verrou si (p == head && tryacquire (arg)) {// mettez à jour le nœud de tête Sethead (nœud); P.Next = null; Échec = false; Retour Vrai; } // Une fois l'heure de temps d'expiration utilisée, quittez directement la boucle if (nanostimeout <= 0) {return false; } // Si l'heure du délai d'expiration est supérieure au temps de rotation, après avoir jugé que le fil peut être suspendu, le fil sera suspendu pendant un certain temps si (devrait-ilarkafterfailedacquire (p, nœud) && nanostimeout> spinformeouthreshold) {// a le thread actuel pendant une période de temps, puis de se réveiller en soi de son locksupport.parknanos ( } // Obtenez l'heure actuelle du système Long Now = System.NanoTime (); // L'heure du délai d'expiration est soustraite de l'intervalle de temps du verrouillage d'acquisition nanostimeout - = maintenant - dernier; // Mette à jour la dernière fois à nouveau dernier = maintenant; // L'exception est lancée lorsqu'une demande d'interruption est reçue lors de l'acquisition du verrou if (Thread.Interrupted ()) {Throw New InterruptedException (); }}} enfin {if (échoué) {Cancelacquire (node); }}}La définition de l'acquisition de temps de temps mort acquérira d'abord de la serrure. Après l'échec de l'acquisition, il sera basé sur la situation. Si le délai de temps mort entrant est supérieur au temps de spin, le fil sera suspendu pendant une période, sinon il tournera. Une fois que chaque fois que le verrou est acquis, le délai d'expiration sera soustrait du temps pris pour acquérir le verrou. Jusqu'à ce que le délai d'expiration soit inférieur à 0, cela signifie que le délai d'expiration a été utilisé. Ensuite, le fonctionnement de l'acquisition de la serrure sera résilié et le drapeau de l'échec de l'acquisition sera retourné. Notez que lors du processus d'acquisition du verrou avec le temps de temps mort, vous pouvez répondre aux demandes d'interruption du thread.
4. Comment le thread libère-t-il le verrou et quitte la file d'attente de synchronisation?
// Opération de verrouillage de libération (mode exclusif) publique publique booléen final (int arg) {// Tournez le verrouillage du mot de passe pour voir s'il peut déverrouiller si (tryrelease (arg)) {// Obtenez le nœud de nœud de tête h = tête; // Si le nœud de tête n'est pas vide et que l'état d'attente n'est pas égal à 0, réveillez le nœud successeur if (h! = Null && h.waitstatus! = 0) {// Réveillez le nœud successeur UnparkSuccessor (h); } return true; } return false;} // réveiller le nœud successeur private void unparksuccessor (nœud nœud) {// Obtenez l'état d'attente du nœud donné int ws = node.waitstatus; // Mettez à jour l'état d'attente à 0 if (ws <0) {comparabledSetWaitStatus (noeud, ws, 0); } // Obtenez le nœud suivant du nœud de nœud donné S = node.next; // Le nœud successeur est vide ou l'état d'attente est annulé si (s == null || s.WaitStatus> 0) {s = null; // Terminez le premier nœud qui n'est pas annulé à partir de la file d'attente de parcours en arrière pour (nœud t = tail; t! = Null && t! = Node; t = t.prev) {if (t.waitStatus <= 0) {s = t; }}} // Réveille le premier nœud après un nœud donné qui n'est pas un état d'annulation si (s! = Null) {locksupport.unpark (s.thread); }}Une fois que le fil tient le verrou dans la pièce, il fera ses propres affaires. Une fois le travail terminé, il libérera la serrure et quittera la pièce. Le verrouillage du mot de passe peut être déverrouillé via la méthode TryRelease. Nous savons que la méthode TryRelease doit être écrasée par la sous-classe. Les règles d'implémentation de différentes sous-classes sont différentes, ce qui signifie que les mots de passe définis par différentes sous-classes sont différents. Par exemple, dans Reentrantlock, chaque fois que la personne de la salle appelle la méthode TryRelease, l'état sera réduit de 1, jusqu'à ce que l'état soit réduit à 0, le verrouillage du mot de passe sera ouvert. Renseignez-vous si ce processus semble tourner constamment la roue du verrouillage du mot de passe, et le nombre de roues est réduit de 1 chaque fois que nous le faisons pivoter. CountdownLatch est un peu similaire à celui-ci, sauf que ce n'est pas seulement qu'il transforme une personne, mais qu'il va renverser une personne, concentrant la force de chacun pour ouvrir la serrure. Après que le fil ait quitté la pièce, il trouvera son siège d'origine, c'est-à-dire trouver le nœud de tête. Voyez si quelqu'un a laissé une petite note sur le siège. S'il y en a, il saura que quelqu'un dort et doit lui demander de le réveiller, puis il réveillera ce fil. Sinon, cela signifie que personne n'attend dans la file d'attente de synchronisation pour le moment, et personne n'en a besoin pour se réveiller, donc il peut partir avec la tranquillité d'esprit. Le processus ci-dessus est le processus de libération du verrouillage en mode exclusif.
Remarque: Toute l'analyse ci-dessus est basée sur JDK1.7, et il y aura des différences entre différentes versions, les lecteurs doivent faire attention.
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.