Les méthodes wait , notify et notifyAll de la classe Object servent à coordonner des threads. Les méthodes wait (il y en a trois) mettent en attente le thread en cours d'exécution et les méthodes notify et notifyAll servent à interrompre cette attente.
Les méthodes wait définies dans la classe Object sont :
public void wait();
public void wait(long maxMilli);
public void wait(long maxMilli, int maxNano);
Avec la deuxième méthode, le thread attend au plus maxMilli millisecondes ; avec la troisième méthode, cette limite est plus fine : le temps maximum est de maxMilli millisecondes auxquelles s'ajoutent maxNano nanosecondes. Nous traiterons ci-dessous de la première de ces instructions wait ; tout ce qui sera dit est aussi valide pour les deux autres.
La méthode wait doit être invoquée à l'intérieur d'une méthode ou d'un bloc synchronisé sur l'objet concerné. Typiquement, on pourra écrire :
synchronized(unObjet) { ... while (!condition) unObjet.wait(); ... }Il est important de noter que lorsque la méthode wait suspend le thread en cours d'exécution, le verrou sur l'objet (ici unObjet ) est rouvert.
Nous dirons que le thread qui exécute l'instruction wait est « mis en attente » par unObjet. L'instruction unObjet.wait() signifie que unObjet met en attente le thread en cours d'exécution (ce n'est pas unObjet qui attend, ce qui d'ailleurs n'aurait pas de sens). Il est fréquent que la condition porte sur les attributs de l'objet sur lequel la synchronisation s'effectue, mais ce n'est pas obligatoire. Si le bloc n'était pas synchronisé, il se pourrait par exemple que, au moment du test de la boucle while, la condition soit vraie, puis qu'un autre thread intervienne et change la valeur de condition avant que ne s'exécute les instructions suivantes qui pourraient se fonder sur l'hypothèse que condition est vraie.
Il faut que l'attente d'un thread puisse cesser, c'est-à-dire que le thread puisse être réveillé ; les instructions notify, et notifyAll le permettent. Pour réveiller un thread qui attend et qui a été mis en attente par unObjet, on doit procéder de la façon suivante :
synchronized(memeObjetQuUnObjet) { ... memeObjetQuUnObjet.notify(); ... }où unObjet et memeObjetQuUnObjet réfèrent au même objet.
Si plusieurs threads ont été mis en attente par unObjet, l'instruction notify() invoquée par unObjet réveille l'un d'entre eux de façon arbitraire. Pour réveiller tous les threads bloqués par unObjet, il faut remplacer notify() par notifyAll(). Il est souvent préférable d'utiliser la méthode notifyAll sinon on risque de ne pas réveiller le thread que l'on souhaite réveiller.
Imaginons qu'une instruction notify() réveille le thread mis en attente dans la boucle
while(!condition) unObjet.wait();
du début de ce paragraphe. Le thread réveillé attend (encore, mais pour d'autres raisons) de pouvoir fermer le verrou de unObjet (puisque la boucle se trouve dans une portion de code synchronisée par unObjet) et, quand c'est le cas, il reprend son cours en testant condition. Si la valeur est true , le thread poursuit ; s'il vaut false , le thread est à nouveau mis en attente par unObjet et le verrou est réouvert..
Au moment de la méthode wait , l'ouverture du verrou et la suspension du thread sont atomiques, c'est-à-dire qu'elles se produisent de façon indivisible ; si ce n'était pas le cas, il se pourrait par exemple que le verrou soit ouvert, puis qu'une notification arrive, puis que le thread soit suspendu ; la notification serait alors sans effet, elle serait perdue. C'est pour éviter ce type de danger que le langage a imposé la contrainte d'invoquer les méthodes wait et notify qui interviennent dans la suspension et pour la reprise d'un même thread dans des blocs synchronisés sur un même objet.
La reprise d'un thread bloqué par une instruction join nécessite une notification ; il faut se méfier du fait que le thread qui a été suspendu par une instruction join peut quelquefois recevoir une notification faite par l'instruction notify (et donc pour un seul thread) qui était destinée, dans l'idée du programmeur, à un autre thread.