408 votes

Java: notify () et notifyAll () encore une fois

Si l'un de Google pour "la différence entre notify() et notifyAll()", puis beaucoup d'explications pop up (en laissant à part la javadoc paragraphes). Tout se résume au nombre de threads en attente de se faire réveiller: l'une en notify() et tous, en notifyAll().

Cependant (si je comprends bien la différence entre ces méthodes à droite), un seul thread est toujours sélectionnée pour suivre de plus près l'acquisition; dans le premier cas, celui choisi par la VM, dans le second cas, l'un choisi par le système planificateur de threads. La sélection exacte des procédures pour les deux d'entre eux (dans le cas général) ne sont pas connus pour le programmeur.

Quelle est l' utilité de la différence entre notify() et notifyAll() alors? Ai-je raté quelque chose?

360voto

xagyg Points 4281

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).

263voto

Liedman Points 3144

Tout simplement parce que cela dépend de pourquoi votre threads sont en attente pour être notifié. Voulez-vous dire à un des threads en attente que quelque chose s'est passé, ou voulez-vous dire à tous en même temps?

Dans certains cas, tous les threads en attente peut prendre utile d'action une fois que l'attente de finitions. Un exemple serait un ensemble de threads en attente pour une certaine tâche à la fin; une fois que la tâche est terminée, tous les threads en attente peuvent continuer leurs affaires. Dans un tel cas, vous devrez utiliser notifyAll() pour réveiller tous les threads en attente en même temps.

Un autre cas, par exemple mutuellement exclusif de verrouillage, seulement l'un des threads en attente peut faire quelque chose d'utile, après avoir été informé (dans ce cas acquérir le verrou). Dans un tel cas, vous utilisez plutôt notify(). Correctement mis en œuvre, vous pourriez utiliser notifyAll() dans ce genre de situation, mais vous serait inutilement sillage des threads qui ne peut rien faire de toute façon.

47voto

David Crow Points 7704

Utile différences:

  • Utilisation notify() si tous les threads en attente sont interchangeables (de l'ordre de réveil n'a pas d'importance), ou si vous ne jamais en avoir un thread en attente. Un exemple courant est celui d'un pool de threads utilisés pour exécuter des travaux à partir d'une file d'attente quand un travail est ajouté, un des fils est notifié à se réveiller, exécutez la tâche suivante et aller de nouveau au sommeil.

  • Utilisation notifyAll() pour d'autres cas où les threads en attente peuvent avoir des objectifs différents et doivent être en mesure d'exécuter simultanément. Un exemple est une opération de maintenance sur une ressource partagée, où plusieurs threads sont en attente pour la fin de l'opération avant d'accéder à la ressource.

19voto

andyuk Points 9464

Je pense que cela dépend de la façon dont les ressources sont produites et consommées. Si 5 objets de travail sont disponibles à la fois et vous avez 5 objets de consommation, il serait judicieux de se réveiller tous les threads à l'aide de notifyAll (), afin que chacun peut traiter 1 objet de travail.

Si vous avez juste un objet de travail disponibles, ce qui est le point de me réveiller tous les objets de consommation à la course pour qu'un objet? La première vérification de la disponibilité de travail obtiendront, et tous les autres threads de rechercher et de trouver qu'ils n'ont rien à faire.

J'ai trouvé une bonne explication ici. En bref:

Le notify() la méthode est généralement utilisée pour les pools de ressources, où il n'y sont un nombre arbitraire de "consommateurs" ou "travailleurs" qui ont des ressources, mais lorsqu'une ressource est ajoutée à la piscine, seulement l'un de l'attente des consommateurs ou les travailleurs peuvent traiter avec elle. L' notifyAll() la méthode est en fait utilisé dans la plupart des autres cas. Strictement, il est tenus de notifier les serveurs d'un condition qui pourrait permettre à plusieurs serveurs de procéder. Mais c'est souvent difficile de savoir. De manière générale la règle, si vous n'avez pas de particulier logique pour l'utilisation de notify(), alors vous devriez probablement utiliser notifyAll(), parce qu'il est souvent difficile de savoir exactement ce que des threads en attente sur un objet particulier et pourquoi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X