83 votes

Pourquoi wait() doit-il toujours être appelé à l'intérieur d'une boucle ?

J'ai lu qu'il fallait toujours appeler un wait() à l'intérieur d'une boucle :

while (!condition) { obj.wait(); }

Cela fonctionne bien sans boucle, pourquoi ?

80voto

kd304 Points 8369

Vous devez non seulement faire une boucle, mais aussi vérifier votre condition dans la boucle. Java ne garantit pas que votre thread sera réveillé uniquement par un appel notify()/notifyAll() ou par le bon appel notify()/notifyAll(). En raison de cette propriété, la version sans boucle peut fonctionner dans votre environnement de développement et échouer dans l'environnement de production de manière inattendue.

Par exemple, vous attendez quelque chose :

synchronized (theObjectYouAreWaitingOn) {
   while (!carryOn) {
      theObjectYouAreWaitingOn.wait();
   }
}

Un fil maléfique arrive et.. :

theObjectYouAreWaitingOn.notifyAll();

Si le fil maléfique ne veut/peut pas perturber la carryOn vous continuez à attendre le client approprié.

Editer : Ajout de quelques échantillons supplémentaires. L'attente peut être interrompue. Il lève InterruptedException et vous pouvez avoir besoin d'envelopper l'attente dans un try-catch. En fonction de vos besoins, vous pouvez quitter ou supprimer l'exception et poursuivre l'attente.

41voto

Tadeusz Kopec Points 7625

La réponse se trouve dans documentation pour Object.wait(long milis)

Un thread peut également se réveiller sans en être informé, sans être interrompu ou sans respecter les délais, ce que l'on appelle un réveil intempestif (spurious wakeup). Bien que cela se produise rarement dans la pratique, les applications doivent s'en prémunir en testant la condition qui aurait dû provoquer le réveil du thread et en continuant à attendre si la condition n'est pas remplie. En d'autres termes, les attentes doivent toujours avoir lieu dans des boucles, comme celle-ci :

 synchronized (obj) {
     while (<condition does not hold>)
         obj.wait(timeout);
     ... // Perform action appropriate to condition
 }

( voir la section 3.2.3 de l'ouvrage de Doug Lea intitulé "Concurrent Programming in Java (deuxième édition)" de Doug Lea (Addison-Wesley, 2000), ou l'article 50 de l'ouvrage de Joshua Bloch intitulé Joshua Bloch, "Effective Java Programming Language Guide" de Joshua Bloch (Addison-Wesley, 2001).

18voto

Gray Points 58585

Pourquoi wait() doit-il toujours être appelé à l'intérieur d'une boucle ?

La première raison pour laquelle while Les boucles sont si importantes que les conditions de course entre les threads. Certes, les réveils intempestifs sont réels et, sur certaines architectures, ils sont fréquents, mais les conditions de course sont une raison beaucoup plus probable pour les réveils intempestifs. while boucle.

Par exemple :

synchronized (queue) {
    // this needs to be while
    while (queue.isEmpty()) {
       queue.wait();
    }
    queue.remove();
}

Avec le code ci-dessus, il peut y avoir 2 threads de consommateurs. Lorsque le producteur verrouille le queue en outre, le consommateur n° 1 peut être bloqué à l'entrée de l'usine. synchronized pendant que le consommateur n°2 attend le queue . Lorsque l'élément est ajouté à la file d'attente et notify appelé par le producteur, #2 est déplacé de la file d'attente pour être bloqué sur le queue mais il s'agira d'une derrière le consommateur n°1 qui était déjà bloqué sur la serrure. Cela signifie que le consommateur n° 1 est le premier à appeler la fonction remove() de la queue . Si le while n'est qu'une boucle if puis, lorsque le consommateur n° 2 obtient le verrou après le consommateur n° 1 et appelle remove() une exception se produirait parce que le queue est maintenant vide -- l'autre thread de consommateurs a déjà retiré l'objet. Même s'il a été notifié, il doit s'assurer que l'objet a été retiré. queue n'est certainement pas vide à cause de cette condition de course.

Ceci est bien documenté. Voici une page web que j'ai créée il y a quelque temps et qui explique le fonctionnement du système. la condition de course en détail et contient des exemples de code.

14voto

Philipp Points 2117

Il peut y avoir plus d'un travailleur qui attend qu'une condition devienne vraie.

Si deux travailleurs ou plus se réveillent (notifyAll), ils doivent à nouveau vérifier la condition. Sinon, tous les travailleurs continueraient même s'il n'y avait de données que pour l'un d'entre eux.

10voto

user104309 Points 587

Je pense avoir obtenu la réponse de @Gray.

Permettez-moi d'essayer de reformuler cela pour les néophytes comme moi et de demander aux experts de me corriger si je me trompe.

Bloc synchronisé avec le consommateur : :

synchronized (queue) {
    // this needs to be while
    while (queue.isEmpty()) {
       queue.wait();
    }
    queue.remove();
}

Bloc synchronisé du producteur : :

synchronized(queue) {
 // producer produces inside the queue
    queue.notify();
}

Supposons que les événements suivants se produisent dans l'ordre indiqué :

1) le consommateur n° 2 pénètre dans le consommateur synchronized et est en attente puisque la file d'attente est vide.

2) Maintenant, le producteur obtient le verrou sur queue et l'insère dans la file d'attente et appelle notify().

Maintenant, on peut choisir de faire fonctionner soit le consommateur n° 1 qui attend queue pour entrer dans la synchronized pour la première fois

ou

le consommateur n° 2 peut être choisi pour fonctionner.

3) disons que le consommateur n° 1 est choisi pour poursuivre l'exécution. Lorsqu'il vérifiera la condition, celle-ci sera vraie et il remove() de la file d'attente.

4) disons que le consommateur n° 2 reprend son exécution là où il l'a interrompue (la ligne après le symbole wait() ). Si la condition "while" n'est pas présente (à la place d'un if ), il procédera simplement à l'appel de remove() ce qui pourrait entraîner une exception ou un comportement inattendu.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X