Grâce à l'analyse dans l'article précédent, nous savons qu'il existe trois façons d'acquérir des verrous avec un mode exclusif, à savoir obtenir sans interruption de thread de réponse, d'obtenir des interruptions de filetage de réponse et d'obtenir un délai d'attente. Il existe également ces trois façons d'acquérir des verrous en mode partagé, et ils sont fondamentalement les mêmes. Si nous déterminons dans un sens, nous pouvons rapidement comprendre d'autres façons. Bien que le code source AbstractQueuedSynchronizer ait plus de mille lignes, il est également répété plusieurs fois, donc les lecteurs ne doivent pas avoir peur au début. Il suffit de le lire patiemment et lentement, vous le comprendrez naturellement progressivement. Dans mon expérience personnelle, il existe plusieurs aspects plus critiques à comprendre lors de la lecture du code source AbstractQueuedynchronizer, à savoir la différence entre le mode exclusif et le mode partagé, l'état d'attente des nœuds et la compréhension des files d'attente conditionnelles. Si vous comprenez ces points clés, la lecture du code source ultérieur sera beaucoup plus facile. Bien sûr, ceux-ci sont introduits dans mon article "Java Concurrency Series [1] ---- AbstractqueueEdSynchronizer Source Code Analysis", et les lecteurs peuvent le vérifier en premier. Cet article analyse le mode de partage de trois façons d'acquérir des verrous et une façon de libérer des verrous.
1. Ne répondant pas à l'acquisition d'interruption du fil
// Acquérir le verrou en mode non interruptible (mode partagé) public final void acquireRared (int arg) {// 1. Essayez d'acquérir le verrou si (tryacQuireShared (arg) <0) {// 2. Si l'acquisition échoue, entrez cette méthode DoacQuirshared (Arg); }} // Essayez d'acquérir le verrouillage (mode partagé) // Nombre négatif: indique que l'échec de l'acquisition // valeur zéro: indique que le nœud actuel est acquis avec succès, mais le nœud successeur ne peut plus obtenir // Numéro positif: indique que le nœud actuel est acquis avec succès, et le nœud successeur peut également obtenir un succès de réussite (INT Arg)Appeler la méthode acquisered est un moyen d'acquérir le verrou sans répondre aux interruptions de thread. Dans cette méthode, TryAcQuirsared est d'abord appelé pour essayer d'acquérir la serrure. La méthode TryAcquireshared renvoie un état d'acquisition de la serrure. Ici, AQS spécifie que si l'état de retour est négatif, cela signifie que le nœud actuel ne parvient pas à acquérir le verrou. Si 0 signifie que le nœud actuel acquiert le verrou, mais le nœud suivant ne peut plus être acquis. S'il est positif, cela signifie que le nœud actuel acquiert la serrure et que les nœuds suivants de cette serrure peuvent également être obtenus avec succès. Lorsqu'une sous-classe met en œuvre la logique d'obtenir des verrous par la méthode TryAcquireshared, la valeur de retour doit se conformer à cette convention. Si la valeur de retour de l'appel TryAcquireShared est inférieure à 0, cela signifie que la tentative d'acquérir le verrou a échoué. Ensuite, appelez la méthode DoAcquireRared pour ajouter le thread actuel à la file d'attente de synchronisation. Nous voyons la méthode Doacquireshared.
// get (mode partagé) dans la file d'attente de synchronisation private void doacquirerared (int arg) {// ajouter à la file d'attente de synchronisation nœud nœud = addWaitter (node.shared); booléen a échoué = true; essayez {booléen interrompu = false; pour (;;) {// Obtenez le nœud avant du nœud final du nœud actuel p = node.predecessor (); // Si le nœud avant est un nœud de tête, essayez à nouveau d'acquérir le verrou si (p == tête) {// essayez d'acquérir le verrou à nouveau et de retourner l'état d'acquisition // R <0, indiquant que l'acquisition a échoué // r = 0 acquis avec succès int r = tryacquirerared (arg); Si (r> = 0) {// à cette fin, cela indique que le nœud actuel a acquis avec succès le verrou. À l'heure actuelle, il propagera les informations d'état de verrouillage au nœud suivant SetheadandPropagate (Node, R); P.Next = null; // Si une demande d'interruption est reçue pendant le blocage du thread, répondez à la demande à cette étape si (interrompue) {selfinterrupt (); } échoué = false; retour; }} // Chaque fois que l'acquisition de verrouillage échoue, il déterminera si le thread peut être suspendu. Si cela est possible, le fil sera suspendu dans la méthode ParkAndCheckInterrupt si (HaudParkAfterFailedAcquire (p, node) && ParkandCheckInterrupt ()) {interrupted = true; }}} enfin {if (échoué) {Cancelacquire (node); }}}En entrant d'abord la méthode DoAcquirearedAred, appelez la méthode AddWaitter pour envelopper le thread actuel dans un nœud et le mettre à la fin de la file d'attente de synchronisation. Nous avons parlé du processus d'ajout de nœuds lors de la parole du mode exclusif, donc je n'en parlerai pas ici. Une fois qu'un nœud est entré dans la file d'attente de synchronisation, s'il constate que le nœud devant lui est le nœud de tête, car le fil du nœud de tête a acquis le verrou et est entré dans la pièce, alors c'est son tour d'acquérir le verrou. Par conséquent, le nœud actuel ne s'accrochera pas en premier, mais essaiera d'acquérir à nouveau le verrou. Si la personne à l'avant libère simplement la serrure et part, le nœud actuel peut obtenir avec succès le verrou. Si la personne en face n'a pas publié le verrou, il appellera la méthode de ParkafterFailedacquire. Dans cette méthode, l'état du nœud de tête sera changé en signal. Ce n'est qu'en veillant à ce que l'état du nœud précédent soit le signal que le nœud actuel peut s'accrocher en toute confiance. Tous les threads seront suspendus dans la méthode ParkandCheckInterrupt. Si le nœud actuel arrive à acquérir avec succès le verrou, alors la méthode SetheadandPropagate sera appelée pour se définir en tant que nœud de tête et réveiller le nœud qui est également le mode partagé derrière. Jetons un coup d'œil à l'opération spécifique de la méthode SetheadandPropagate.
// Définissez le nœud de tête et propagez l'état du verrouillage (mode partagé) private void SetheadandPropagate (nœud nœud, int propagate) {nœud h = head; // Définissez le nœud donné comme le nœud de tête Sethead (nœud); // Si le propagat est supérieur à 0, cela signifie que le verrou peut obtenir if (propagat> 0 || h == null || h.waitstatu <0) {// Obtenez le nœud successeur du nœud de nœud donné s = node.next; // Si le nœud successeur du nœud donné est vide, ou si son état est un état partagé if (s == null || s.isshared ()) {// réveiller le nœud successeur doreEleaseshared (); }}} // Opération de verrouillage de libération (mode partagé) private void doreESeshared () {for (;;) {// Obtenez le nœud de tête du nœud de file d'attente synchrone h = tête; if (h! = null && h! = tail) {// Obtenez l'état d'attente du nœud de tête int ws = H.WaitStatus; // Si l'état du nœud de tête est le signal, cela signifie que quelqu'un fait la queue derrière if (ws == node.signal) {// Obtenez l'état d'attente du nœud de tête à 0 if (! CompareAndSetwaitStatus (h, node.signal, 0)) {continuer; } // Réveille le nœud successeur Unparksuccessor (H); // Si l'état du nœud de tête est 0, cela signifie que personne ne fait la queue plus tard, modifiez simplement l'état de tête pour propager} else if (ws == 0 &&! CompareAndSetWaitSatus (h, 0, node.propagate)) {continuant; }} // En vous assurant que le nœud de tête n'a pas été modifié pendant la période, vous pouvez sortir de la boucle if (h == Head) {Break; }}}L'appel de la méthode SetheadandPropagate se présente d'abord comme le nœud de tête, puis décide de réveiller le nœud successeur en fonction de la valeur de retour de la méthode TryAcquirsared. Comme mentionné précédemment, lorsque la valeur de retour est supérieure à 0, cela signifie que le nœud actuel a acquis avec succès le verrou et le nœud suivant peut également acquérir avec succès le verrou. À l'heure actuelle, le nœud actuel doit réveiller le nœud qui est également dans le mode partagé. Notez que chaque fois que vous vous réveillez, il ne s'agit que de réveiller le nœud suivant. Si ce dernier nœud n'est pas dans le mode partagé, le nœud actuel entrera directement dans la pièce et ne réveillera pas le nœud supplémentaire. Le fonctionnement du réveil des nœuds successeurs en mode partagé est effectué dans la méthode DoreleSeshared. Les opérations de réveil du mode partagé et du mode exclusive sont fondamentalement les mêmes. Les deux trouvent la marque sur votre siège (état d'attente). Si la marque est signal, cela signifie que quelqu'un doit aider à le réveiller plus tard. Si la marque est 0, cela signifie que personne ne fait la queue dans la file d'attente pour le moment. En mode exclusif, si vous constatez que personne ne fait la queue, vous quitterez directement la file d'attente. En mode partagé, si vous constatez que personne ne fait la queue derrière la file d'attente, le nœud actuel laissera toujours une petite note avant de partir (définissez le statut d'attente pour se propager) pour indiquer aux personnes ultérieures l'état disponible de cette serrure. Ensuite, lorsque la personne qui vient plus tard peut juger de l'opportunité d'acquérir directement la serrure en fonction de cet état.
2. Réponse à l'acquisition d'interruption du thread
// Acquérir le verrou en mode interruptible (mode partagé) public final void acquiresharedAnderruply (int arg) lance InterruptedException {// Déterminez d'abord si le thread est interrompu, si c'est le cas, jetez une exception si (thread.interrupted ()) {throw new interruptedException (); } // 1. Essayez d'acquérir le verrou si (tryacQuireShared (arg) <0) {// 2. Si l'acquisition échoue, entrez cette méthode DoacQuirsareDeredInterruply (Arg); }} // Acquérir en mode interruptible (mode partagé) private void doacQuirsaredAredInterruply (int arg) interruptedException {// insérer le nœud actuel dans la queue de la file d'attente de synchronisation Node final Node = addWaitter (node.shared); booléen a échoué = true; try {for (;;) {// Obtenez le nœud final du nœud précédent p = node.predecessor (); if (p == Head) {int r = tryacQuireRared (arg); if (r> = 0) {SetheadAndPropagate (node, r); P.Next = null; Échec = false; retour; }} if (houstparkafterFailedAcquire (p, node) && ParkandCheckInterrupt ()) {// Si le thread reçoit une demande d'interruption pendant le processus de blocage, il lancera immédiatement une exception ici lancera une nouvelle interruption de l'interruption (); }}} enfin {if (échoué) {Cancelacquire (node); }}}La manière d'acquérir une serrure en réponse aux interruptions de thread et la manière d'acquérir un verrou en réponse aux interruptions de thread est essentiellement la même dans le processus. La seule différence est de savoir où répondre aux demandes d'interruption du thread. Lorsque l'interruption du fil ne répond pas à l'interruption du fil pour acquérir le verrou, le fil est éveillé à partir de la méthode ParkandCheckInterrupt. Après le réveil, il renvoie immédiatement si la demande d'interruption a été reçue. Même si la demande d'interruption est reçue, elle continuera de tourner jusqu'à ce qu'elle ait été acquise jusqu'à ce qu'elle réponde à la demande d'interruption et s'accroche. Le thread répondra immédiatement à la demande d'interruption après l'éveil du thread. Si l'interruption du thread est reçue pendant le processus de blocage, une interruption d'Exception sera immédiatement lancée.
3. Réglez le délai d'attente pour obtenir
// Acquérir le verrou avec un délai limité (mode partagé) final booléen tryacquireresharednanos (int arg, long nanostimeout) lève l'interruptException {if (thread.interrupted ()) {throw new interruptedException (); } // 1. Appeler TryacquireShared pour essayer d'acquérir la serrure // 2. Si l'acquisition échoue, appelez DoAcquiresharedNanos renvoyez TryAcQuirsared (arg)> = 0 || DoacquiresharedNanos (arg, nanostimeout);} // acquérir la serrure avec un temps mort limité (mode partagé) booléen privé doacquirearedarenanos (int arg, long nanostimeout) lève interruptedException {long dernier = System.Nanotime (); Node nœud final = addWaitter (node.shared); booléen a échoué = true; try {for (;;) {// Obtenez le nœud précédent du nœud de nœud actuel p = node.predecessor (); if (p == Head) {int r = tryacQuireRared (arg); if (r> = 0) {SetheadAndPropagate (node, r); P.Next = null; Échec = false; Retour Vrai; }} // Si le délai d'attente est utilisé, l'acquisition sera résiliée et les informations de défaillance seront renvoyées si (nanostimeout <= 0) {return false; } // 1. Vérifiez si l'exigence de suspension du thread est satisfaite (garantie que l'état du nœud avant est le signal) // 2. Vérifiez si le délai d'attente est supérieur au temps de spin si (MaudParkAfterFailedAcquire (P, Node) && nanostimeout> SpinForttimeoutThreshold) {// Si les deux conditions ci-dessus sont remplies, le thread actuel sera suspendu pendant une période de lockSupport.Parknanos (this, nanostimeout); } long maintenant = System.NanoTime (); // Timeout Time à chaque fois que l'heure du nanostime de verrouillage Nanostimeout - = Now - Last Time; Last fois = maintenant; // Si une demande d'interruption est reçue pendant le blocage, une exception sera immédiatement lancée si (thread.interrupted ()) {throw new InterruptedException (); }}} enfin {if (échoué) {Cancelacquire (node); }}}Si vous comprenez les deux méthodes d'acquisition ci-dessus, il sera très facile de définir la méthode d'acquisition du temps d'attente. Le processus de base est le même, principalement pour comprendre le mécanisme du délai d'expiration. Si la serrure est acquise pour la première fois, la méthode Doacquiresharednanos sera appelée et le délai d'attente sera passé. Après être entré dans la méthode, la serrure sera à nouveau acquise en fonction de la situation. Si le verrouillage échoue, le fil doit être considéré comme suspendu. À l'heure actuelle, nous déterminerons si le délai d'expiration est supérieur au temps de rotation. Si c'est le cas, le fil sera suspendu pendant un certain temps. Sinon, nous continuerons à essayer de l'obtenir. Après chaque fois que nous acquérons le verrou, nous soustraire le temps de la serrure pour l'acquérir. Nous allons boucler comme ça jusqu'à ce que l'heure du délai soit épuisé. Si le verrou n'a pas été acquis, l'acquisition sera interrompue et le drapeau de l'échec de l'acquisition sera retourné. Le fil réagit aux interruptions du fil tout au long de la période.
4. Déliètement des opérations des nœuds en mode partagé
// Fonctionnement de la libération de verrouillage (mode partagé) public final booléen releaseshared (int arg) {//1.try pour libérer le verrou if (tryreleSeshared (arg)) {// 2. Si la version réussit, réveillez-vous d'autres fils DoreleSeshared (); Retour Vrai; } return false;} // Essayez de libérer le verrou (mode partagé) booléen protégé TryReleSeshared (int arg) {lancez un nouveau non supporté OperationException ();} // Fonctionnement de la libération de verrouillage (mode partagé) privé void doreleaseshared () {pour (; if (h! = null && h! = tail) {// Obtenez l'état d'attente du nœud de tête int ws = H.WaitStatus; // Si le statut du nœud de tête est le signal, cela signifie que quelqu'un fait la queue plus tard si (ws == node.signal) {// Mettez à jour d'abord l'état d'attente du nœud de tête à 0 if (! CompareAndSetwaitStatus (h, node.signal, 0)) {continue; } // Réveille le nœud ultérieur Unparksuccessor (H); // Si le statut du nœud de tête est 0, cela signifie que personne ne fait la queue plus tard, il modifie simplement l'état de tête pour propager} else if (ws == 0 &&! CompareAndSetWaitSatus (h, 0, node.propagate)) {continuant; }} // La boucle ne peut être brisée que si (h == tête) {break; }}}Une fois le fil terminé le travail dans la salle, il appellera la méthode de relance pour libérer le verrou. Tout d'abord, il appellera la méthode TryReleSeshared pour essayer de libérer le verrou. La logique de jugement de cette méthode est implémentée par la sous-classe. Si la version réussit, appelez la méthode DoreleSeshared pour réveiller le nœud successeur. Après avoir quitté la pièce, il trouvera le siège d'origine (nœud de tête) et verra si quelqu'un a laissé de petites notes sur le siège (signal d'état). Si c'est le cas, réveillez le nœud successeur. S'il n'y a pas (statut 0) signifie que personne ne fait la queue dans la file d'attente, alors la dernière chose qu'il doit faire avant de partir est de partir, ce qui est de laisser une petite note sur son siège (le statut est défini pour se propager) pour dire aux personnes derrière la serrure pour acquérir l'état. La seule différence entre l'ensemble du processus de libération de verrouillage et le mode exclusif est de fonctionner dans cette dernière étape.
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.