Quels sont les dommages potentiels s'il était possible d'invoquer en wait()
dehors d'un bloc synchronisé, en conservant sa sémantique - en suspendant le thread appelant?
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
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();
}
}
C'est ce qui pourrait potentiellement arriver:
Un thread consommateur appelle take()
et voit que le buffer.isEmpty()
.
Avant que le thread consommateur continue à appeler wait()
, un thread producteur arrive et appelle un complet give()
, c'est-à-direbuffer.add(data); notify();
Le thread consommateur va maintenant appeler wait()
(et rater le notify()
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'il notify
n'est jamais appelé entre isEmpty
et wait
.
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:
Vous avez besoin d'une garantie absolue que le serveur et le notifiant s'accordent sur l'état du prédicat. Le serveur vérifie légèrement l'état du prédicat à un moment donné AVANT qu'il ne se mette en veille, mais cela dépend pour l'exactitude du fait que le prédicat est vrai QUAND il se met en veille. Il y a une période de vulnérabilité entre ces deux événements, ce qui peut interrompre 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 l'attente et la notification sont effectuées en synchronized
blocs.
Ce message a été réécrit en tant qu'article ici: Java: Pourquoi attendre doit être appelé dans un bloc synchronisé