1
votes

pourquoi y a-t-il une boucle while dans put () de LinkedBlockingQueue

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}
why is there a while loop?All the putting thread is shut out by putLock.No thread can increase 'count' when the waiting thread is holding the putLock.

0 commentaires

3 Réponses :


0
votes

La fonction de Loop (*) bloque le thread qui est appelé méthode put lorsque la capacité de LinkedBlockingQueue est pleine. Lorsqu'un autre thread appelle la méthode take (ou poll), il y aura de l'espace pour le nouvel élément dans la file d'attente et la méthode take signalera la condition notFull , et le thread en attente sera réveillé et pourra mettre l'élément dans la file d'attente.

(*) La condition de la boucle est de garantir qu'un faux réveil ne s'est pas produit.

https://en.wikipedia.org/wiki/Spurious_wakeup


5 commentaires

Je pense qu'il n'y a qu'un seul thread en attente à cause de notFull.await (). Pas "l'un des". ?


@Syn plusieurs threads peuvent atteindre ce point. Mais quand ils seront notifiés, un seul d'entre eux pourra continuer, les autres devront attendre la prochaine notification, (alors encore une fois, un seul d'entre eux continuera.)


pourquoi «plusieurs threads peuvent atteindre ce point». ? Un thread peut acquérir le putLock, d'autres attendent parce que 'putLock.lockInterruptably ()'


Plusieurs threads peuvent atteindre ce point. Voir la réponse de @ Holger. notFull est une condition attachée à putLock . Lorsque vous appelez notFull.awat () , cela libère le verrou putLock @Syn.


@Gray Merci. Je n'ai jamais utilisé de conditions auparavant. Il y a toujours quelque chose à apprendre je suppose.



4
votes

Il existe une propriété fondamentale de await (qui s'applique au verrouillage intrinsèque via synchronized et en utilisant également Object.wait ), vous doivent comprendre:

Lorsque vous invoquez wait , vous libérez le verrou cette Condition est associée à¹. Il n'y a aucun moyen de contourner cela, sinon personne ne pourrait acquérir le verrou, rendre la condition remplie et invoquer signal dessus.

Lorsque votre thread en attente est signalé, il ne récupère pas le verrou immédiatement. Ce ne serait pas possible, car le thread qui a invoqué signal le possède toujours. Au lieu de cela, le récepteur essaiera de réacquérir le verrou, ce qui n'est pas très différent d'appeler lockInterruptiblement () .

Mais ce thread n'est pas nécessairement le seul thread essayant d'acquérir le verrou. Il n’est même pas nécessaire que ce soit le premier. Un autre thread aurait pu arriver à put avant la signalisation et attendre le verrou à lockInterruptably () . Ainsi, même si le verrou était correct (ce qui n'est généralement pas le cas), le thread signalé n'avait pas de priorité. Même si vous avez donné la priorité aux threads signalés, plusieurs threads peuvent être signalés pour différentes raisons.

Donc, un autre thread arrivant à put pourrait obtenir le verrou avant le thread signalé, trouver qu'il y a de la place et stocker l'élément sans jamais se soucier des signaux. Ensuite, au moment où le thread signalé a acquis le verrou, la condition n'est plus remplie. Ainsi, un thread signalé ne peut jamais se fier à la validité de la condition simplement parce qu'il a reçu un signal et doit donc revérifier la condition et invoquer à nouveau wait s'il n'est pas rempli.

Cela fait de la vérification de la condition dans une boucle l'idiome standard de l'utilisation de await , comme documenté dans l'interface Condition , ainsi que Object.wait pour le cas d'utilisation le moniteur intrinsèque, juste pour être complet. En d'autres termes, ce n'est même pas spécifique à une API particulière.

Puisque la condition doit être pré-vérifiée et re-vérifiée de toute façon dans une boucle, la spécification permet même des faux réveils , l'événement d'un thread revenant de l'opération d'attente sans réellement recevoir un signal. Cela peut simplifier les implémentations de verrouillage de certaines plates-formes, sans changer la façon dont un verrou doit être utilisé.

¹ Il est important de souligner que lorsque vous maintenez plusieurs verrous, seul le verrou associé à la condition est libéré. ​​


7 commentaires

Je sais ce qu'est un faux échec en cas de lowCompareAndSet , est-ce la même chose pour faux réveils ? Est-ce que vous savez?


@Eugene Je ne sais pas quels détails d'implémentation particuliers les développeurs avaient à l'esprit. Je me souviens vaguement avoir lu quelque chose sur une bibliothèque de threads Linux, mais à la fin, cela n'a pas d'importance, car la façon dont cela fonctionne, cela peut toujours se comporter comme si de faux réveils pouvaient se produire, c'est-à-dire que vous avez de toute façon besoin de la boucle.


Spot sur. des réveils parasites peuvent se produire sur certaines architectures où les threads peuvent être réveillés sans recevoir réellement le signal. Par exemple, certaines architectures réveillent toutes les conditions lorsqu'elles sont signalées à cause de l'implémentation.


Une chose à souligner est que notFull est une condition qui vient de putLock donc lorsque vous appelez spécifiquement notFull.await () , cela déverrouille < code> putLock . Je sais que vous le dites, mais pensez à le rendre plus évident.


@Gray Je pense que je sais où vous vous dirigez. Ajout d'une déclaration explicite.


Mon point était que l'OP peut ne pas comprendre que notFull et putLock sont connectés. Au lieu de parler de await () , je dirais que notFull.await () provoque le déverrouillage du putLock ....


@Gray Je pense qu'il suffit de souligner que les conditions sont associées à un verrou et libéreront ce verrou (et uniquement ce verrou). Une fois que cela est compris, le lecteur comprend également la nécessité de savoir quel verrou est associé à une condition, pour bien comprendre le code source. Pour la question réelle «pourquoi y a-t-il une boucle», il est totalement indifférent de savoir quel verrou est libéré, en fait, l'existence de deux verrous dans cette classe est une distraction. Pour les débutants, il est préférable de regarder ArrayBlockingQueue , qui n'a qu'un seul Lock , mais qui, bien sûr, a encore besoin de la boucle.



0
votes

La réponse de @ Holder est correcte, mais j'aimerais ajouter plus de détails sur le code et la question suivants.

private final Condition notFull = putLock.newCondition();

Pourquoi y a-t-il une boucle while? Tout le fil de mise est fermé par putLock.

La boucle while est une partie critique de ce modèle de code qui garantit que lorsque le thread est réveillé à partir d'un signal sur notFull , il s'assure qu'un autre thread n'est pas arrivé là d'abord et remplissez le tampon.

Une chose dont il est important de se rendre compte que le notFull est défini comme une condition sur putLock : p>

putLock.lockInterruptibly();
try {
    while (count.get() == capacity) {
        notFull.await();
    }
    ...

Lorsque le thread appelle notFull.await () il va déverrouiller putLock ce qui signifie que plusieurs threads peuvent exécuter notFull.await () en même temps. Un thread ne tentera de réacquérir le verrou qu'après l'appel de notFull.signal () (ou signalAll () ).

La condition de concurrence se produit si thread-A est BLOQUÉ essayant d'acquérir putLock et thread-B est WAITING sur notFull . Si un thread-C supprime quelque chose de la file d'attente et signale notFull , le thread-B sera retiré de la file d'attente et placé dans la file d'attente bloquée sur putLock mais malheureusement, il sera derrière le thread-A qui était déjà bloqué. Donc, une fois que putLock est déverrouillé, le thread-A va acquérir putLock et mettre quelque chose dans la file d'attente, le remplissant à nouveau. Lorsque thread-B acquiert finalement putLock , il doit tester à nouveau pour voir s'il reste de l'espace disponible avant de mettre (et de déborder) la file d'attente. C'est pourquoi le while est nécessaire.

Une raison secondaire pour la boucle while, comme @Holder l'a également mentionné, est de se protéger contre les réveils parasites qui peuvent se produire sous certaines architectures de thread quand un la condition est signalée artificiellement. Par exemple, sous certaines architectures, un signal sur n'importe quelle condition signale toutes les conditions en raison des limitations du système d'exploitation.


0 commentaires