11 votes

notify() se comporte comme notifyAll()

J'ai un thread Calculator qui calcule la somme des nombres de 1 à 50, et plusieurs threads Reader qui affichent le résultat une fois que le thread Calculator est prêt. J'ai la possibilité d'appeler notify() et notifyAll() pour signaler aux lecteurs que le résultat du calcul est prêt à être affiché. À la ligne B de la classe Calculator, si j'appelle la méthode notifyAll(), le résultat est imprimé 4 fois comme prévu. Mais lorsque je remplace la ligne B par la seule méthode notify(), le résultat est toujours imprimé 4 fois, ce qui ne semble pas correct. Je crois comprendre que notify() ne réveille qu'un seul des threads en attente, pas tous. Pourquoi tous les threads se réveillent-ils et impriment-ils le résultat lorsque j'appelle notify ?

public class ThreadWaitNotify {

    public static void main(String[] args) {
        Calculator c = new Calculator();
        Reader r = new Reader(c);
        Reader r2 = new Reader(c);
        Reader r3 = new Reader(c);
        Reader r4 = new Reader(c);

        r.start();
        r2.start();
        r3.start();
        r4.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        c.start();
    }

}

Classe de lecteur :

class Reader extends Thread {

    Calculator c;

    public Reader(Calculator c) {
        this.c = c;
    }

    @Override
    public void run() {
        synchronized (c) {
            try {
                System.out.println(Thread.currentThread().getName() + " Waiting for calculations: ");
                c.wait();    // LINE A
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Total: " + c.getSum());
        }
    }
}

Classe de calculatrice :

class Calculator extends Thread {

    private int sum = 0;

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 1; i <= 50; i++) {
                sum += i;
            }
            notify();  // LINE B
        }
    }

    public int getSum() {
        return sum;
    }
}

Sortie :

Thread-1 Waiting for calculations: 
Thread-4 Waiting for calculations: 
Thread-3 Waiting for calculations: 
Thread-2 Waiting for calculations: 
Thread-1 Total: 1275
Thread-2 Total: 1275
Thread-3 Total: 1275
Thread-4 Total: 1275

\======================

UPDATE : L'utilisation d'un objet comme moniteur/verrouillage au lieu d'une instance de Thread produit le comportement correct.

Mise à jour de la classe ThreadWaitNotify :

public class ThreadWaitNotify {

    public static void main(String[] args) {
        Object monitor = new Object();
        Calculator c = new Calculator(monitor);
        Reader r = new Reader(c, monitor);
        Reader r2 = new Reader(c, monitor);
        Reader r3 = new Reader(c, monitor);
        Reader r4 = new Reader(c, monitor);

        r.start();
        r2.start();
        r3.start();
        r4.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        c.start();
    }

}

Classe de lecteur mise à jour :

class Reader extends Thread {

    Calculator c;
    Object monitor;

    public Reader(Calculator c, Object monitor) {
        this.c = c;
        this.monitor = monitor;
    }

    @Override
    public void run() {
        synchronized (monitor) {
            try {
                System.out.println(Thread.currentThread().getName() + " Waiting for calculations: ");
                monitor.wait();   // LINE A
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Total: " + c.getSum());
        }
    }
}

Classe de calculatrice mise à jour :

class Calculator extends Thread {

    private int sum = 0;
    Object monitor;

    public Calculator(Object monitor) {
        this.monitor = monitor;
    }

    @Override
    public void run() {
        synchronized (monitor) {
            for (int i = 1; i <= 50; i++) {
                sum += i;
            }
            monitor.notify();       // LINE B
        }
    }

    public int getSum() {
        return sum;
    }
}

9voto

John Vint Points 19804

Ma première réponse était fausse. J'ai juste parcouru le code natif pour trouver ce qui se passe.

Lorsqu'un thread se termine, il notifie en fait tous les threads en attente sur le moniteur du thread sortant. Encore une fois, ceci est au niveau natif, et peut changer

thread.cpp ->

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
      ..... other code ....
      // Notify waiters on thread object. This has to be done after exit() is called
      // on the thread (if the thread is the last thread in a daemon ThreadGroup the
      // group should have the destroyed bit set before waiters are notified).
      ensure_join(this);
      assert(!this->has_pending_exception(), "ensure_join should have cleared");
     .... other code ....

Il s'agit de la source du JDK 7, mais cette fonctionnalité, je ne peux pas imaginer, sera très différente.

0voto

Aman Goyal Points 119

La réponse d'Izruo est juste. Je voudrais y ajouter quelque chose. Si vous placez sleep() en dehors du bloc synchronisé, alors l'un des threads se réveillera et les autres seront en état d'attente.

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