Clairement, notify
se réveille (tout) un thread dans l'attente jeu, notifyAll
se réveille tous les threads en attente d'ensemble. La discussion qui suit devrait effacer tous les doutes. notifyAll
devrait être utilisé la plupart du temps. Si vous n'êtes pas sûr à utiliser, puis utilisez notifyAll
.Veuillez voir l'explication qui suit.
Lire très attentivement et de comprendre. Merci de m'envoyer un email si vous avez des questions.
Regardez producteur/consommateur (hypothèse est une ProducerConsumer classe avec les deux méthodes). IL EST CASSÉ (car il utilise notify
) - oui, il PEUT travailler - même la plupart du temps, mais il peut également provoquer un blocage - nous allons voir pourquoi:
public synchronized void put(Object o) {
while (buf.size()==MAX_SIZE) {
wait(); // called if the buffer is full (try/catch removed for brevity)
}
buf.add(o);
notify(); // called in case there are any getters or putters waiting
}
public synchronized Object get() {
// Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
while (buf.size()==0) {
wait(); // called if the buffer is empty (try/catch removed for brevity)
// X: this is where C1 tries to re-acquire the lock (see below)
}
Object o = buf.remove(0);
notify(); // called if there are any getters or putters waiting
return o;
}
Tout d'ABORD,
Pourquoi avons-nous besoin d'une boucle while entourant l'attendre?
Nous avons besoin d'un while
boucle dans le cas où nous obtenons cette situation:
Consommateurs 1 (C1) entrez le bloc synchronisé et le tampon est vide, alors C1 est mis dans l'attente set (via l' wait
appel). Consommation 2 (C2) est sur le point d'entrer dans la méthode synchronisée (au point d'Y ci-dessus), mais le Producteur P1 met un objet dans la mémoire tampon, et par la suite des appels notify
. Le seul thread en attente C1, de sorte qu'il est réveillé et maintenant les tentatives de ré-acquérir l'objet de verrouillage au point X (ci-dessus).
Maintenant, C1 et C2 sont de tenter d'acquérir le verrou de synchronisation. L'un d'eux (nondeterministically) est choisi et entre la méthode, l'autre est bloqué (pas d'attente, mais bloqué, en essayant d'acquérir le verrou sur la méthode). Disons C2 obtient le verrou de la première. C1 est toujours de blocage (en essayant d'acquérir le verrou à l'X). C2 complète de la méthode et libère le verrou. Maintenant, C1 acquiert le verrou. Devinez quoi, la chance nous avons un while
boucle, parce que, C1 effectue la boucle de vérification (la garde) et est empêché de retrait d'un inexistante élément de la mémoire tampon (C2 déjà eu!). Si nous n'avions pas un while
, on obtient un IndexArrayOutOfBoundsException
en C1 essaie de supprimer le premier élément de la mémoire tampon!
MAINTENANT,
Ok, maintenant, pourquoi avons-nous besoin de notifyAll?
Dans le producteur/consommateur exemple ci-dessus, il semble que nous pouvons en tirer avec notify
. Il semble de cette façon, parce que nous pouvons prouver que les gardiens de l' attente des boucles pour le producteur et le consommateur sont mutuellement exclusifs. C'est, il semble que nous ne peut pas avoir un thread en attente dans l' put
méthode ainsi que l' get
méthode, parce que, pour que cela soit vrai, les mesures suivantes devront être remplies:
buf.size() == 0 AND buf.size() == MAX_SIZE
(à supposer MAX_SIZE n'est pas 0)
CEPENDANT, ce n'est pas assez bon, nous avons BESOIN d'utiliser notifyAll
. Nous allons voir pourquoi ...
Supposons que nous avons une mémoire tampon de taille 1 (pour prendre l'exemple facile à suivre). Les étapes suivantes nous conduire à une impasse. Notez qu'à chaque fois qu'un thread est réveillé avec informer, il peut être non-déterministe sélectionné par la JVM - qui est de toute attente thread peut être réveillé. Notez également que lorsque plusieurs threads sont le blocage de l'entrée à une méthode (c'est à dire en essayant d'acquérir un verrou), l'ordre d'acquisition peut être non-déterministe. Rappelez-vous aussi qu'un thread ne peut être dans une des méthodes à la fois - la synchronisation des méthodes de permettre à un seul thread à exécuter (c'est à dire la tenue de l'écluse de l') (synchronisé) méthodes dans la classe. Si les phénomènes suivants se produit: les résultats du blocage:
ÉTAPE 1:
- P1 met 1 char dans la mémoire tampon
ÉTAPE 2:
- P2 tentatives put
- contrôles de boucle d'attente - déjà un char - attend
ÉTAPE 3:
- P3 tentatives put
- contrôles de boucle d'attente - déjà un char - attend
ÉTAPE 4:
- C1 tentatives pour obtenir 1 char
- C2 tentatives pour obtenir 1 char - blocs à l'entrée de l' get
méthode
- C3 tentatives pour obtenir 1 char - blocs à l'entrée de l' get
méthode
ÉTAPE 5:
- C1 est de l'exécution de la get
méthode obtient le char, les appels notify
, sorties méthode
- notify
Se réveille P2
- MAIS, C2 entre la méthode avant de P2 (P2 doivent acquérir le verrou), donc P2 blocs à l'entrée de l' put
méthode
- C2 contrôles de boucle d'attente, pas plus de caractères dans le tampon, de sorte attend
- C3 entre la méthode d'après C2, mais avant de P2, contrôles de boucle d'attente, pas plus de caractères dans le tampon, de sorte attend
ÉTAPE 6:
- MAINTENANT: il y a P3, C2, et C3 en attente!
- Enfin P2 acquiert le verrou, met un char dans la mémoire tampon, les appels de notification, les sorties méthode
ÉTAPE 7:
- P2 de la notification réveille P3 (souvenez-vous de n'importe quel thread peut être réveillé)
- P3 vérifie la boucle d'attente de condition, il y a déjà un caractère dans le tampon, de sorte que les temps d'attente.
- PAS PLUS de THREADS POUR APPELER INFORMER et TROIS FILS SUSPENDUS!
SOLUTION: Remplacez notify
avec notifyAll
dans le producteur/code de la consommation (ci-dessus).