Quel est le dommage potentiel s'il était possible d'invoquer wait()
en dehors d'un bloc synchronisé, en conservant sa sémantique - en suspendant le thread appelant ?
Illustrons les problèmes auxquels nous serions confrontés si wait()
pourrait être appelé en dehors d'un bloc synchronisé avec un exemple concret .
Supposons que nous mettions en place une file d'attente bloquante (je sais, il y en a déjà une dans l'API :)
Une première tentative (sans synchronisation) pourrait ressembler à ce qui suit
class BlockingQueue {
Queue<String> buffer = new LinkedList<String>();
public void give(String data) {
buffer.add(data);
notify(); // Since someone may be waiting in take!
}
public String take() throws InterruptedException {
while (buffer.isEmpty()) // don't use "if" due to spurious wakeups.
wait();
return buffer.remove();
}
}
Voilà ce qui pourrait se passer :
-
Un thread consommateur appelle take()
et voit que le buffer.isEmpty()
.
-
Avant que le thread du consommateur n'appelle wait()
un fil de producteur arrive et invoque une pleine give()
c'est-à-dire, buffer.add(data); notify();
-
Le thread du consommateur va maintenant appeler wait()
(et miss le site notify()
qui vient d'être appelé).
-
S'il est malchanceux, le fil producteur ne produira pas plus de give()
en raison du fait que le thread du 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 s'assurer notify
n'est jamais appelé entre isEmpty
y wait
.
Sans entrer dans les détails : Ce problème de synchronisation est universel. Comme le fait remarquer Michael Borgwardt, wait/notify concerne la communication entre les threads, et vous finirez toujours par avoir une situation de course similaire à celle décrite ci-dessus. C'est la raison pour laquelle la règle "only wait inside synchronized" est appliquée.
Un paragraphe du lien posté par @Willie résume assez bien la situation :
Vous avez besoin d'une garantie absolue que le serveur et le notificateur sont d'accord sur l'état du prédicat. Le serveur vérifie l'état du prédicat à un moment légèrement AVANT qu'il ne s'endorme, mais il dépend pour être correct que le prédicat soit vrai QUAND il s'endort. Il y a une période de vulnérabilité entre ces deux événements, ce qui peut casser le programme.
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 le wait et le notify sont exécutés en synchronized
blocs.
Ce post a été réécrit sous forme d'article ici : Java : Pourquoi wait doit être appelé dans un bloc synchronisé