Question

Si l'on recherche sur Google "la différence entre notify() et notifyAll()" alors de nombreuses explications apparaîtront (en laissant de côté les paragraphes javadoc).Tout se résume au nombre de threads en attente réveillés :un dans notify() et tout dedans notifyAll().

Cependant (si je comprends bien la différence entre ces méthodes), un seul thread est toujours sélectionné pour une acquisition ultérieure du moniteur ;dans le premier cas celui sélectionné par la VM, dans le second cas celui sélectionné par le planificateur de threads système.Les procédures de sélection exactes pour les deux (dans le cas général) ne sont pas connues du programmeur.

Quel est le utile différence entre notifier() et notifierTout() alors?Est-ce que j'ai raté quelque chose ?

Était-ce utile?

La solution

Cependant (si je comprends bien la différence entre ces méthodes), un seul thread est toujours sélectionné pour une acquisition ultérieure du moniteur.

Ce n'est pas correct. o.notifyAll() se réveille tous des threads qui sont bloqués dans o.wait() appels.Les discussions ne sont autorisées à revenir que depuis o.wait() un par un, mais chacun volonté ont leur tour.


En termes simples, cela dépend de la raison pour laquelle vos discussions attendent d'être notifiées.Voulez-vous dire à l'un des fils de discussion en attente que quelque chose s'est passé, ou voulez-vous le dire à tous en même temps ?

Dans certains cas, tous les threads en attente peuvent entreprendre des actions utiles une fois l'attente terminée.Un exemple serait un ensemble de threads attendant la fin d’une certaine tâche ;une fois la tâche terminée, tous les threads en attente peuvent continuer leurs activités.Dans un tel cas, vous utiliseriez notifierTout() pour réveiller tous les threads en attente en même temps.

Autre cas, par exemple un verrouillage mutuellement exclusif, un seul des threads en attente peut faire quelque chose d'utile après avoir été notifié (dans ce cas acquérir le verrou).Dans un tel cas, vous préférez utiliser notifier().Correctement mis en œuvre, vous pourrait utiliser notifierTout() dans cette situation également, mais vous réveilleriez inutilement des threads qui ne peuvent rien faire de toute façon.


Dans de nombreux cas, le code pour attendre une condition sera écrit sous forme de boucle :

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

De cette façon, si un o.notifyAll() l'appel réveille plus d'un thread en attente et le premier à revenir du o.wait() fait laisse la condition dans l'état faux, puis les autres threads qui ont été réveillés reviendront en attente.

Autres conseils

Clairement, notify réveille (n'importe quel) thread dans l'ensemble d'attente, notifyAll réveille tous les threads de l’ensemble en attente.La discussion suivante devrait dissiper tous les doutes. notifyAll devrait être utilisé la plupart du temps.Si vous ne savez pas lequel utiliser, utilisez notifyAll.Veuillez consulter l’explication qui suit.

Lisez très attentivement et comprenez.Veuillez m'envoyer un e-mail si vous avez des questions.

Regardez producteur/consommateur (l’hypothèse est une classe ProducerConsumer avec deux méthodes).IL EST CASSÉ (car il utilise notify) - oui cela PEUT fonctionner - même la plupart du temps, mais cela peut aussi provoquer des blocages - nous verrons 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;
}

PREMIÈREMENT,

Pourquoi avons-nous besoin d’une boucle while entourant l’attente ?

Nous avons besoin d'un while boucle au cas où nous obtiendrions cette situation :

Le consommateur 1 (C1) entre dans le bloc synchronisé et le tampon est vide, donc C1 est mis dans l'ensemble d'attente (via le wait appel).Le consommateur 2 (C2) est sur le point d'entrer dans la méthode synchronisée (au point Y ci-dessus), mais le producteur P1 place un objet dans le tampon, puis appelle notify.Le seul thread en attente est C1, il est donc réveillé et tente maintenant de réacquérir le verrou d'objet au point X (ci-dessus).

C1 et C2 tentent maintenant d'acquérir le verrou de synchronisation.L'un d'eux (de manière non déterministe) est choisi et entre dans la méthode, l'autre est bloqué (n'attendant pas - mais bloqué, essayant d'acquérir le verrou sur la méthode).Disons que C2 obtient le verrou en premier.C1 bloque toujours (essayant d'acquérir le verrou en X).C2 termine la méthode et libère le verrou.Désormais, C1 acquiert le verrou.Devinez quoi, heureusement que nous avons un while boucle, car C1 effectue la vérification de boucle (garde) et ne peut pas supprimer un élément inexistant du tampon (C2 l'a déjà compris !).Si nous n'avions pas de while, nous obtiendrions un IndexArrayOutOfBoundsException car C1 essaie de supprimer le premier élément du tampon !

MAINTENANT,

Ok, maintenant pourquoi avons-nous besoin de notifyAll ?

Dans l’exemple producteur/consommateur ci-dessus, il semble que nous puissions nous en sortir notify.Cela semble être le cas, car nous pouvons prouver que les gardes sur le attendez les boucles pour le producteur et le consommateur s’excluent mutuellement.Autrement dit, il semble que nous ne pouvons pas avoir de thread en attente dans le put méthode ainsi que la get méthode, car pour que cela soit vrai, il faudrait que ce qui suit soit vrai :

buf.size() == 0 AND buf.size() == MAX_SIZE (supposons que MAX_SIZE n'est pas 0)

CEPENDANT, ce n'est pas suffisant, nous DEVONS utiliser notifyAll.Voyons pourquoi...

Supposons que nous ayons un tampon de taille 1 (pour rendre l'exemple facile à suivre).Les étapes suivantes nous conduisent à une impasse.Notez qu'à TOUT MOMENT, un thread est réveillé avec notify, il peut être sélectionné de manière non déterministe par la JVM - c'est-à-dire que n'importe quel thread en attente peut être réveillé.Notez également que lorsque plusieurs threads bloquent l'entrée dans une méthode (c'est-à-direessayant d'acquérir une serrure), l'ordre d'acquisition peut être non déterministe.N'oubliez pas également qu'un thread ne peut être que dans l'une des méthodes à la fois - les méthodes synchronisées permettent à un seul thread de s'exécuter (c'est-à-diredétenant le verrou de) toutes les méthodes (synchronisées) de la classe.Si la séquence d'événements suivante se produit, il en résulte un blocage :

ÉTAPE 1:
- P1 met 1 caractère dans le tampon

ÉTAPE 2:
- Tentatives P2 put - vérifie la boucle d'attente - déjà un caractère - attend

ÉTAPE 3:
- Tentatives P3 put - vérifie la boucle d'attente - déjà un caractère - attend

ÉTAPE 4:
- C1 tente d'obtenir 1 caractère
- C2 tente d'obtenir 1 caractère - bloque l'entrée dans le get méthode
- C3 tente d'obtenir 1 caractère - bloque l'entrée dans le get méthode

ÉTAPE 5 :
- C1 exécute le get méthode - obtient le caractère, appelle notify, quitte la méthode
- Le notify réveille P2
- MAIS, C2 entre dans la méthode avant que P2 ne le puisse (P2 doit réacquérir le verrou), donc P2 bloque à l'entrée du put méthode
- C2 vérifie la boucle d'attente, plus de caractères dans le tampon, donc attend
- C3 entre dans la méthode après C2, mais avant P2, vérifie la boucle d'attente, plus de caractères dans le tampon, donc attend

ÉTAPE 6 :
- MAINTENANT:il y a P3, C2 et C3 qui attendent !
- Enfin P2 acquiert le verrou, met un caractère dans le tampon, appelle notify, quitte la méthode

ÉTAPE 7 :
- La notification de P2 réveille P3 (rappelez-vous que n'importe quel thread peut être réveillé)
- P3 vérifie la condition de la boucle d'attente, il y a déjà un caractère dans le tampon, donc attend.
- PLUS DE FILS À APPELER POUR NOTIFIER et TROIS FILS SUSPENDU DE MANIÈRE PERMANENTE !

SOLUTION:Remplacer notify avec notifyAll dans le code producteur/consommateur (ci-dessus).

Différences utiles :

  • Utiliser notifier() si tous vos threads en attente sont interchangeables (l'ordre dans lequel ils se réveillent n'a pas d'importance), ou si vous n'avez qu'un seul thread en attente.Un exemple courant est un pool de threads utilisé pour exécuter des tâches à partir d'une file d'attente : lorsqu'une tâche est ajoutée, l'un des threads est invité à se réveiller, à exécuter la tâche suivante et à se rendormir.

  • Utiliser notifierTout() pour d'autres cas où les threads en attente peuvent avoir des objectifs différents et devraient pouvoir s'exécuter simultanément.Un exemple est une opération de maintenance sur une ressource partagée, où plusieurs threads attendent la fin de l'opération avant d'accéder à la ressource.

Je pense que cela dépend de la manière dont les ressources sont produites et consommées.Si 5 objets de travail sont disponibles à la fois et que vous disposez de 5 objets consommateurs, il serait logique de réveiller tous les threads à l'aide de notifyAll() afin que chacun puisse traiter 1 objet de travail.

Si vous n'avez qu'un seul objet de travail disponible, quel est l'intérêt de réveiller tous les objets consommateurs pour qu'ils courent pour cet objet unique ?Le premier qui vérifie le travail disponible l'obtiendra et tous les autres threads vérifieront et découvriront qu'ils n'ont rien à faire.

j'ai trouvé un bonne explication ici.En bref:

La méthode notify () est généralement utilisée pour pools de ressources, où il y a un nombre arbitraire de «consommateurs» ou de «travailleurs» qui prennent des ressources, mais lorsqu'une ressource est ajoutée au pool, un seul des consommateurs ou des travailleurs en attente peut y faire face.La méthode notifyall () est en fait utilisée dans la plupart des autres cas.Strictement, il est nécessaire d'informer les serveurs d'une condition qui pourrait permettre à plusieurs serveurs de procéder.Mais c'est souvent difficile à connaître.Donc, en règle générale, Si vous n'avez pas de logique particulière pour utiliser notify (), vous devez probablement utiliser notifyall (), car il est souvent difficile de savoir exactement quels threads attendront sur un objet particulier et pourquoi.

Notez qu'avec les utilitaires de concurrence, vous avez également le choix entre signal() et signalAll() comme ces méthodes sont appelées ici.La question reste donc valable même avec java.util.concurrent.

Doug Lea soulève un point intéressant dans son livre célèbre:si un notify() et Thread.interrupt() se produire en même temps, la notification pourrait en fait être perdue.Si cela peut se produire et a des implications dramatiques notifyAll() est un choix plus sûr même si vous payez le prix des frais généraux (réveiller trop de threads la plupart du temps).

De Joshua Bloch, le gourou Java lui-même dans Effective Java 2e édition :

"Point 69 :Préférez les utilitaires de concurrence pour attendre et notifier".

Voici un exemple.Exécuter.Ensuite, remplacez l'un des notifyAll() par notify() et voyez ce qui se passe.

Classe ProducerConsumerExample

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

Classe Dropbox

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

Classe de consommation

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

Classe de producteur

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

Court résumé:

Préférez toujours notifierTout() sur notifier() sauf si vous disposez d'une application massivement parallèle dans laquelle un grand nombre de threads font tous la même chose.

Explication:

notifier() ...] réveille un seul fil.Parce que notifier() Ne vous permet pas de spécifier le fil qui est réveillé, il n'est utile que dans des applications massivement parallèles - c'est-à-dire des programmes avec un grand nombre de threads, tous des tâches similaires.Dans une telle application, peu importe quel thread est réveillé.

source: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Comparer notifier() avec notifierTout() dans la situation décrite ci-dessus :une application massivement parallèle où les threads font la même chose.Si tu appelles notifierTout() dans ce cas, notifierTout() provoquera le réveil (c'est-à-direplanification) d'un très grand nombre de threads, dont beaucoup inutilement (puisqu'un seul thread peut réellement continuer, à savoir celui qui se verra accorder le moniteur pour l'objet attendez(), notifier(), ou notifierTout() a été sollicité), gaspillant ainsi des ressources informatiques.

Ainsi, si vous n'avez pas d'application dans laquelle un grand nombre de threads font la même chose simultanément, préférez notifierTout() sur notifier().Pourquoi?Parce que, comme d'autres utilisateurs l'ont déjà répondu sur ce forum, notifier()

réveille un seul thread qui attend sur le moniteur de cet objet....] Le choix est arbitraire et se produit à la discrétion de la mise en œuvre.

source:API Java SE8 (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

Imaginez que vous ayez une application producteur-consommateur dans laquelle les consommateurs sont prêts (c'est-à-dire attendez() ing) à consommer, les producteurs sont prêts (c'est-à-dire attendez() ing) à produire et la file d’attente des éléments (à produire/consommer) est vide.Dans ce cas, notifier() pourrait réveiller uniquement les consommateurs et jamais les producteurs, car le choix de celui qui est réveillé est arbitraire.Le cycle producteur-consommateur ne progresserait pas même si les producteurs et les consommateurs sont respectivement prêts à produire et à consommer.Au lieu de cela, un consommateur est réveillé (c'est-à-direquittant le attendez() statut), ne retire pas un élément de la file d'attente car il est vide, et notifier() C'est un autre consommateur qui doit continuer.

En revanche, notifierTout() réveille à la fois les producteurs et les consommateurs.Le choix des personnes programmées dépend du planificateur.Bien entendu, en fonction de l'implémentation du planificateur, celui-ci peut également planifier uniquement les consommateurs (par ex.si vous attribuez aux threads consommateurs une priorité très élevée).Cependant, l'hypothèse ici est que le danger que le planificateur planifie uniquement les consommateurs est inférieur au risque que la JVM ne réveille que les consommateurs, car tout planificateur raisonnablement implémenté ne fait pas que arbitraire les décisions.Au contraire, la plupart des implémentations de planificateurs font au moins quelques efforts pour éviter la famine.

Je suis très surpris que personne n'ait mentionné le fameux problème du "réveil perdu" (google).

Essentiellement:

  1. si vous avez plusieurs threads en attente sur une même condition et,
  2. plusieurs threads qui peuvent vous faire passer de l'état A à l'état B et,
  3. plusieurs threads qui peuvent vous faire passer de l'état B à l'état A (généralement les mêmes threads qu'en 1.) et,
  4. la transition de l'état A à B doit informer les threads en 1.

ALORS, vous devez utiliser notifyAll, sauf si vous avez des garanties prouvables que les réveils perdus sont impossibles.

Un exemple courant est une file d'attente FIFO simultanée où :plusieurs mises en file d'attente (1.et 3.Ci-dessus) peut transférer votre file d'attente des désueurs multiples vides à plusieurs vides (2.ci-dessus) peut attendre la condition "La file d'attente n'est pas vide" vide -> non vide doit informer les Dequeuers

Vous pouvez facilement écrire un entrelacement d'opérations dans lequel, à partir d'une file d'attente vide, 2 mises en file d'attente et 2 retraits de file d'attente interagissent et 1 mise en file d'attente restera endormie.

Il s’agit d’un problème sans doute comparable au problème de l’impasse.

J'espère que cela dissipera certains doutes.

notifier() :La méthode notify () réveille un fil en attendant le verrou (le premier fil qui a appelé wait () sur ce verrou).

notifierTout() :La méthode notifyAll() réveille tous les threads en attente du verrou ;Le JVM sélectionne l'un des threads dans la liste des threads en attente du verrou et réveille ce filetage.

Dans le cas d'un seul thread en attendant un verrou, il n'y a pas de différence significative entre notify() et notifyAll().Cependant, lorsque plusieurs threads attendent le verrou, dans notify() et notifyAll(), le thread exact réveillé est sous le contrôle de la JVM et vous ne pouvez pas contrôler par programme le réveil d'un thread spécifique.

À première vue, il semble que ce soit une bonne idée d'appeler simplement notify() pour réveiller un thread ;il peut sembler inutile de réveiller tous les threads.Cependant, le problème avec notify() est que le thread réveillé n'est peut-être pas celui qui convient être réveillé (le thread peut attendre une autre condition, ou la condition n'est toujours pas satisfaite pour ce thread, etc.). Dans ce cas, le notify() pourrait être perdu et aucun autre thread ne se réveillera, ce qui pourrait conduire à un type de blocage (la notification est perdue et tous les autres threads attendent une notification – pour toujours).

Pour éviter ce problème, il est toujours préférable d'appeler notifyAll() lorsqu'il y a plus d'un thread en attente d'un verrou (ou plus d'une condition pour laquelle l'attente est effectuée).La méthode notifyAll() réveille tous les threads, elle n'est donc pas très efficace.cependant, cette perte de performances est négligeable dans les applications réelles.

notify() réveillera un thread pendant que notifyAll() va réveiller tout.A ma connaissance, il n'y a pas de juste milieu.Mais si vous n'êtes pas sûr de quoi notify() fera l'affaire sur vos discussions, utilisez notifyAll().Fonctionne à merveille à chaque fois.

Toutes les réponses ci-dessus sont correctes, pour autant que je sache, je vais donc vous dire autre chose.Pour le code de production, vous devriez vraiment utiliser les classes de java.util.concurrent.Ils ne peuvent pas faire grand-chose pour vous dans le domaine de la concurrence en Java.

Voici une explication plus simple :

Vous avez raison, que vous utilisiez notify() ou notifyAll(), le résultat immédiat est qu'exactement un autre thread acquerra le moniteur et commencera à s'exécuter.(En supposant que certains threads aient en fait été bloqués lors de wait() pour cet objet, d'autres threads non liés n'absorbent pas tous les cœurs disponibles, etc.) L'impact vient plus tard.

Supposons que les threads A, B et C attendent cet objet et que le thread A récupère le moniteur.La différence réside dans ce qui se passe une fois que A relâche le moniteur.Si vous avez utilisé notify(), alors B et C sont toujours bloqués dans wait() :ils n'attendent pas sur le moniteur, ils attendent d'être avertis.Lorsque A relâche le moniteur, B et C seront toujours là, attendant une notification ().

Si vous avez utilisé notifyAll(), alors B et C ont tous deux dépassé l'état « en attente de notification » et attendent tous deux d'acquérir le moniteur.Lorsque A libère le moniteur, B ou C l'acquériront (en supposant qu'aucun autre thread n'est en compétition pour ce moniteur) et commenceront à s'exécuter.

Il existe trois états pour un thread.

  1. ATTENDRE - Le thread n'utilise aucun cycle CPU
  2. BLOQUÉ - Le thread est bloqué en essayant d'acquérir un moniteur. Il utilise peut-être encore les cycles du processeur
  3. RUNNING - Le thread est en cours d'exécution.

Désormais, lorsqu'un notify() est appelé, la JVM sélectionne un thread et le déplace vers l'état BLOCKED et donc vers l'état RUNNING car il n'y a pas de concurrence pour l'objet moniteur.

Lorsqu'un notifyAll() est appelé, la JVM sélectionne tous les threads et les déplace tous vers l'état BLOCKED.Tous ces threads obtiendront le verrou de l'objet en priorité. Le thread qui est capable d'acquérir le moniteur en premier pourra d'abord passer à l'état RUNNING et ainsi de suite.

Cette réponse est une réécriture graphique et une simplification de l'excellente réponse par xagyg, y compris les commentaires de ère.

Pourquoi utiliser notifyAll, même lorsque chaque produit est destiné à un seul consommateur ?

Considérons les producteurs et les consommateurs, simplifiés comme suit.

Producteur:

while (!empty) {
   wait() // on full
}
put()
notify()

Consommateur:

while (empty) {
   wait() // on empty
}
take()
notify()

Supposons que 2 producteurs et 2 consommateurs partagent un tampon de taille 1.L'image suivante représente un scénario menant à un impasse, ce qui serait évité si tous les threads étaient utilisés notifierTous.

Chaque notification est étiquetée avec le thread en cours de réveil.

deadlock due to notify

notify() vous permet d'écrire du code plus efficace que notifyAll().

Considérez le morceau de code suivant exécuté à partir de plusieurs threads parallèles :

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

Il peut être rendu plus efficace en utilisant notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

Dans le cas où vous disposez d'un grand nombre de threads, ou si la condition de boucle d'attente est coûteuse à évaluer, notify() sera nettement plus rapide que notifyAll().Par exemple, si vous avez 1 000 threads, 999 threads seront réveillés et évalués après le premier notifyAll(), puis 998, puis 997, et ainsi de suite.Au contraire, avec le notify() solution, un seul thread sera réveillé.

Utiliser notifyAll() lorsque vous devez choisir quel fil fera le travail ensuite :

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

Enfin, il est important de comprendre qu'en cas de notifyAll(), le code à l'intérieur synchronized les blocs qui ont été réveillés seront exécutés séquentiellement, pas tous en même temps.Disons qu'il y a trois threads en attente dans l'exemple ci-dessus et que le quatrième thread appelle notifyAll().Les trois threads seront réveillés mais un seul démarrera l'exécution et vérifiera l'état du while boucle.Si la condition est true, il appellera wait() à nouveau, et alors seulement le deuxième thread commencera à s'exécuter et vérifiera son while condition de boucle, et ainsi de suite.

Pris à partir de Blog sur Java efficace :

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

Donc ce que je comprends c'est (du blog précité, commentaire de "Yann TM" sur réponse acceptée et Java documents):

  • notifier() :JVM réveille l'un des threads en attente sur cet objet.La sélection des sujets est faite arbitrairement et sans équité.Ainsi, le même fil peut être réveillé encore et encore.L'état du système change donc mais aucun progrès réel n'est réalisé.Créant ainsi un livelock.
  • notifyAll() :JVM réveille tous les threads, puis tous les threads se précipitent pour obtenir le verrou sur cet objet.Désormais, le planificateur du processeur sélectionne un thread qui acquiert le verrou sur cet objet.Ce processus de sélection serait bien meilleur que la sélection par JVM.Ainsi, assurer la vivacité.

Jetez un œil au code publié par @xagyg.

Supposons que deux threads différents attendent deux conditions différentes :
Le premier fil attend buf.size() != MAX_SIZE, et le deuxième fil attend buf.size() != 0.

Supposons qu'à un moment donné buf.size() n'est pas égal à 0.Appels JVM notify() au lieu de notifyAll(), et le premier thread est notifié (pas le second).

Le premier thread est réveillé, vérifie buf.size() qui pourrait revenir MAX_SIZE, et recommence à attendre.Le deuxième thread n'est pas réveillé, continue d'attendre et n'appelle pas get().

notify() réveille le premier thread qui a appelé wait() sur le même objet.

notifyAll() réveille tous les threads qui ont appelé wait() sur le même objet.

Le thread ayant la priorité la plus élevée sera exécuté en premier.

Je voudrais mentionner ce qui est expliqué dans Java Concurrency in Practice :

Premier point, que ce soit Notify ou NotifyAll ?

It will be NotifyAll, and reason is that it will save from signall hijacking.

Si deux threads A et B attendent des prédicats de condition différents de la même queue de la file d'attente et de la même condition sont appelés, il est jusqu'à JVM à quel thread JVM notivera.

Maintenant, si la notification était destinée au thread A et à JVM, le thread B notifié B, le thread B se réveillera et verra que cette notification n'est pas utile, elle attendra à nouveau.Et le fil A ne connaîtra jamais ce signal manqué et quelqu'un a détourné sa notification.

Ainsi, appeler Notifyall résoudra ce problème, mais encore une fois, il aura un impact sur les performances car il avisera tous les threads et tous les threads concurrenceront pour le même verrou et il impliquera un commutateur de contexte et donc un chargement sur le processeur.Mais nous ne devons nous soucier des performances que si elle se comporte correctement, si son comportement lui-même n'est pas correct, les performances ne sont pas utiles.

Ce problème peut être résolu en utilisant l'objet Condition du verrouillage explicite Lock, fourni dans jdk 5, car il fournit une attente différente pour chaque prédicat de condition.Ici, il se comportera correctement et il n'y aura pas de problème de performances car il appellera le signal et s'assurera qu'un seul thread attend cette condition.

notify notifiera un seul thread qui est en état d'attente, tandis que notify all notifiera tous les threads en état d'attente, désormais tous les threads notifiés et tous les threads bloqués sont éligibles pour le verrou, parmi lesquels un seul obtiendra le verrou et tous les autres (y compris ceux qui étaient en état d'attente plus tôt) seront en état de blocage.

notify() - Sélectionne un thread aléatoire dans l'ensemble d'attente de l'objet et le place dans le BLOCKED État.Le reste des threads dans l'ensemble d'attente de l'objet est toujours dans le WAITING État.

notifyAll() - Déplace tous les threads de l'ensemble d'attente de l'objet vers BLOCKED État.Après avoir utilisé notifyAll(), il ne reste plus de threads dans l'ensemble d'attente de l'objet partagé car ils sont tous maintenant dans BLOCKED état et non dans WAITING État.

BLOCKED - bloqué pour l'acquisition du verrou.WAITING - en attente de notification (ou bloqué pour la fin de la jointure).

Pour résumer les excellentes explications détaillées ci-dessus, et de la manière la plus simple à laquelle je puisse penser, cela est dû aux limitations du moniteur intégré JVM, qui 1) est acquis sur l'ensemble de l'unité de synchronisation (bloc ou objet) et 2) ne fait aucune distinction quant à la condition spécifique attendue/notifiée.

Cela signifie que si plusieurs threads attendent des conditions différentes et que notify() est utilisé, le thread sélectionné peut ne pas être celui qui progresserait sur la condition nouvellement remplie - ce qui entraînerait ce thread (et d'autres threads actuellement encore en attente qui pourraient pour remplir la condition, etc.) de ne pas pouvoir progresser, et éventuellement de mourir de faim ou de suspendre le programme.

En revanche, notifyAll() permet à tous les threads en attente de réacquérir éventuellement le verrou et de vérifier leur état respectif, permettant ainsi éventuellement de progresser.

Ainsi, notify() ne peut être utilisé en toute sécurité que si un thread en attente est assuré de permettre la progression s'il est sélectionné, ce qui est en général satisfait lorsque tous les threads du même moniteur vérifient une seule et même condition - un phénomène assez rare. cas dans des applications du monde réel.

Lorsque vous appelez wait() de "l'objet" (en attendant que le verrou de l'objet soit acquis), cela libérera le verrou sur cet objet et aidera les autres threads à verrouiller cet "objet", dans ce scénario, il y aura plus d'un thread en attente de la "ressource/objet" (étant donné que les autres threads ont également émis l'attente sur le même objet ci-dessus et qu'en cours de route, il y aura un thread qui remplira la ressource/l'objet et invoquera notify/notifyAll).

Ici, lorsque vous envoyez la notification du même objet (du même côté/de l'autre côté du processus/code), cela libérera un seul thread bloqué et en attente (pas tous les threads en attente - ce thread libéré sera choisi par le thread JVM. Le planificateur et tout le processus d'obtention de verrou sur l'objet sont les mêmes que d'habitude).

Si vous n'avez qu'un seul thread qui partagera/travaillera sur cet objet, vous pouvez utiliser la méthode notify() seule dans votre implémentation wait-notify.

si vous êtes dans une situation où plusieurs threads lisent et écrivent sur des ressources/objets en fonction de votre logique métier, alors vous devriez opter pour notifyAll()

maintenant, je regarde comment exactement le jvm identifie et brise le thread en attente lorsque nous émettons notify() sur un objet...

Bien qu'il existe des réponses solides ci-dessus, je suis surpris par le nombre de confusions et de malentendus que j'ai lus.Cela prouve probablement l'idée selon laquelle il faut utiliser java.util.concurrent autant que possible au lieu d'essayer d'écrire son propre code concurrent cassé.Revenons à la question :pour résumer, la meilleure pratique aujourd'hui est d'ÉVITER notify() dans TOUTES les situations en raison du problème de perte de réveil.Quiconque ne comprend pas cela ne devrait pas être autorisé à écrire du code de concurrence critique.Si le problème du regroupement vous inquiète, un moyen sûr de réveiller un thread à la fois consiste à :1.Créez une file d'attente explicite pour les threads en attente ;2.Demandez à chacun des threads de la file d'attente d'attendre son prédécesseur ;3.Demandez à chaque thread d'appeler notifyAll() une fois terminé.Ou vous pouvez utiliser Java.util.concurrent.*, qui l'a déjà implémenté.

Tout réveiller n’a pas beaucoup d’importance ici.attendez notifier et notifier tout, tout cela est mis après avoir possédé le moniteur de l'objet.Si un thread est en phase d'attente et que notify est appelé, ce thread prendra le verrou et aucun autre thread à ce stade ne pourra prendre ce verrou.L'accès simultané ne peut donc pas avoir lieu du tout.Pour autant que je sache, tout appel à wait notify et notifyall ne peut être effectué qu'après avoir verrouillé l'objet.Corrigez-moi si je me trompe.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top