A wait()
n'a de sens que lorsqu'il y en a aussi notify()
, il s'agit donc toujours de communication entre les threads, et qui nécessite une synchronisation pour fonctionner correctement. On pourrait faire valoir que cela devrait être implicite, mais cela n'aiderait pas vraiment, pour la raison suivante:
Sémantiquement, vous n'êtes jamais juste wait()
. Vous avez besoin d'une condition pour être certifié, et si ce n'est pas le cas, vous attendez qu'elle le soit. Donc ce que vous faites vraiment
if(!condition){
wait();
}
Mais la condition est définie par un thread distinct, donc pour que cela fonctionne correctement, vous avez besoin d'une synchronisation.
Quelques autres choses qui ne vont pas avec cela, où le fait que votre thread quitte l'attente ne signifie pas que la condition que vous recherchez est vraie:
Vous pouvez obtenir de faux réveils (ce qui signifie qu'un thread peut se réveiller sans avoir reçu de notification), ou
La condition peut être définie, mais un troisième thread rend la condition fausse à nouveau au moment où le thread en attente se réveille (et rachète le moniteur).
Pour traiter ces cas, ce dont vous avez vraiment besoin est toujours une variante de ceci:
synchronized(lock){
while(!condition){
lock.wait();
}
}
Mieux encore, ne vous souciez pas du tout des primitives de synchronisation et travaillez avec les abstractions proposées dans les java.util.concurrent
packages.
Michael Borgwardt
la source
Thread.interrupted()
également être vérifiée.Illustrons les problèmes que nous rencontrerions si nous
wait()
pouvions les appeler en dehors d'un bloc synchronisé avec un exemple concret .Supposons que nous devions implémenter une file d'attente de blocage (je sais, il y en a déjà une dans l'API :)
Une première tentative (sans synchronisation) pourrait ressembler à quelque chose comme ci-dessous
C'est ce qui pourrait potentiellement arriver:
Un thread consommateur appelle
take()
et voit que lebuffer.isEmpty()
.Avant que le thread consommateur continue à appeler
wait()
, un thread producteur arrive et appelle un completgive()
, c'est-à-direbuffer.add(data); notify();
Le thread consommateur va maintenant appeler
wait()
(et rater lenotify()
qui vient d'être appelé).En cas de malchance, le thread producteur ne produira pas plus
give()
du fait que le thread consommateur ne se réveille jamais, et nous avons un blocage.Une fois que vous avez compris le problème, la solution est évidente: utilisez
synchronized
pour vous assurer qu'ilnotify
n'est jamais appelé entreisEmpty
etwait
.Sans entrer dans les détails: ce problème de synchronisation est universel. Comme le souligne Michael Borgwardt, attendre / notifier concerne la communication entre les threads, vous vous retrouverez donc toujours avec une condition de concurrence similaire à celle décrite ci-dessus. C'est pourquoi la règle "n'attendre qu'à l'intérieur synchronisée" est appliquée.
Un paragraphe du lien publié par @Willie le résume assez bien:
Le prédicat sur lequel le producteur et le consommateur doivent se mettre d'accord est dans l'exemple ci-dessus
buffer.isEmpty()
. Et l'accord est résolu en s'assurant que l'attente et la notification sont effectuées ensynchronized
blocs.Ce message a été réécrit en tant qu'article ici: Java: Pourquoi attendre doit être appelé dans un bloc synchronisé
la source
return buffer.remove();
dans le bloc mais aprèswait();
, ça marche?wait
retours.Thread.currentThread().wait();
dans lamain
fonction entourée de try-catch pourInterruptedException
. Sanssynchronized
bloc, cela me donne la même exceptionIllegalMonitorStateException
. Qu'est-ce qui le rend maintenant illégal? Cela fonctionne à l'intérieur dusynchronized
bloc.@Rollerball a raison. Le
wait()
est appelé, afin que le thread puisse attendre qu'une condition se produise lorsque cetwait()
appel se produit, le thread est obligé de renoncer à son verrou.Pour abandonner quelque chose, vous devez d'abord le posséder. Le thread doit d'abord posséder le verrou. D'où la nécessité de l'appeler dans un
synchronized
méthode / un bloc.Oui, je suis d'accord avec toutes les réponses ci-dessus concernant les dommages / incohérences potentiels si vous n'avez pas vérifié la condition dans la
synchronized
méthode / le blocage. Cependant, comme l'a souligné @ shrini1000, le simple fait d'appelerwait()
dans un bloc synchronisé n'empêchera pas cette incohérence de se produire.Voici une belle lecture ..
la source
Le problème que cela peut causer si vous le faites synchronisez pas avant
wait()
est le suivant:makeChangeOnX()
et vérifie la condition while, et c'esttrue
(x.metCondition()
retournefalse
, signifiex.condition
estfalse
) donc il y entrera. Puis juste avant lawait()
méthode, un autre thread va àsetConditionToTrue()
et définit lex.condition
àtrue
etnotifyAll()
.wait()
méthode (non affecté par lenotifyAll()
qui s'est passé quelques instants auparavant). Dans ce cas, le 1er thread attendra qu'un autre thread s'exécutesetConditionToTrue()
, mais cela pourrait ne pas se reproduire.la source
Nous savons tous que les méthodes wait (), notify () et notifyAll () sont utilisées pour les communications entre threads. Pour se débarrasser des signaux manqués et des problèmes de réveil parasites, le fil d'attente attend toujours sous certaines conditions. par exemple-
Ensuite, la notification de la variable wasNotified des ensembles de threads à true et notifie.
Chaque thread a son cache local, donc toutes les modifications y sont d'abord écrites puis promues progressivement dans la mémoire principale.
Si ces méthodes n'avaient pas été invoquées dans le bloc synchronisé, la variable wasNotified ne serait pas vidée dans la mémoire principale et serait là dans le cache local du thread, de sorte que le thread en attente continuera d'attendre le signal bien qu'il ait été réinitialisé en notifiant le thread.
Pour résoudre ces types de problèmes, ces méthodes sont toujours invoquées à l'intérieur du bloc synchronisé, ce qui garantit qu'au démarrage du bloc synchronisé, tout sera lu dans la mémoire principale et vidé dans la mémoire principale avant de quitter le bloc synchronisé.
Merci, j'espère que cela clarifiera.
la source
Cela a essentiellement à voir avec l'architecture matérielle (c'est-à-dire la RAM et les caches ).
Si vous n'utilisez pas
synchronized
avecwait()
ounotify()
, un autre thread pourrait entrer dans le même bloc au lieu d'attendre que le moniteur y entre. De plus, lorsque, par exemple, l'accès à un tableau sans bloc synchronisé, un autre thread peut ne pas y voir le changement ... en fait, un autre thread ne verra aucun changement lorsqu'il a déjà une copie du tableau dans le cache de niveau x ( alias caches de 1er / 2e / 3e niveau) du noyau CPU de gestion des threads.Mais les blocs synchronisés ne sont qu'un côté de la médaille: si vous accédez réellement à un objet dans un contexte synchronisé à partir d'un contexte non synchronisé, l'objet ne sera toujours pas synchronisé même dans un bloc synchronisé, car il contient une propre copie du objet dans son cache. J'ai écrit à propos de ces problèmes ici: https://stackoverflow.com/a/21462631 et lorsqu'un verrou contient un objet non final, la référence de l'objet peut-elle toujours être modifiée par un autre thread?
De plus, je suis convaincu que les caches de niveau x sont responsables de la plupart des erreurs d'exécution non reproductibles. C'est parce que les développeurs n'apprennent généralement pas les choses de bas niveau, comme le fonctionnement du processeur ou la façon dont la hiérarchie de la mémoire affecte le fonctionnement des applications: http://en.wikipedia.org/wiki/Memory_hierarchy
Cela reste une énigme pour laquelle les classes de programmation ne commencent pas par la hiérarchie de la mémoire et l'architecture du CPU en premier. "Bonjour tout le monde" n'aidera pas ici. ;)
la source
directement à partir de ce tutoriel java oracle:
la source
Lorsque vous appelez notify () à partir d'un objet t, java notifie une méthode t.wait () particulière. Mais, comment java recherche et notifie une méthode d'attente particulière.
java ne regarde que le bloc de code synchronisé qui a été verrouillé par l'objet t. java ne peut pas rechercher le code entier pour notifier un t.wait () particulier.
la source
selon les documents:
wait()
signifie simplement qu'elle libère le verrou sur l'objet. Ainsi, l'objet sera verrouillé uniquement dans le bloc / méthode synchronisé. Si le thread est en dehors du bloc de synchronisation signifie qu'il n'est pas verrouillé, s'il n'est pas verrouillé, que feriez-vous sur l'objet?la source
Attente du thread sur l' objet de surveillance (objet utilisé par le bloc de synchronisation), Il peut y avoir n nombre d'objets de surveillance dans tout le trajet d'un seul thread. Si le thread attend en dehors du bloc de synchronisation, il n'y a aucun objet de surveillance et aucun autre thread ne notifie l'accès à l'objet de surveillance, alors comment le thread en dehors du bloc de synchronisation saura-t-il qu'il a été notifié. C'est également l'une des raisons pour lesquelles wait (), notify () et notifyAll () sont dans la classe objet plutôt que dans la classe thread.
Fondamentalement, l'objet de surveillance est une ressource commune ici pour tous les threads, et les objets de surveillance ne peuvent être disponibles que dans le bloc de synchronisation.
la source