web-dev-qa-db-fra.com

Comment les méthodes wait () et notify () peuvent-elles être appelées sur des objets qui ne sont pas des threads?

Comment les méthodes wait() et notify() peuvent-elles être appelées sur des objets qui ne sont pas des threads? Cela n'a pas vraiment de sens, n'est-ce pas?

Cela doit sûrement avoir un sens, cependant, car les deux méthodes sont disponibles pour tous les objets Java. Quelqu'un peut-il fournir une explication? J'ai du mal à comprendre comment communiquer entre les threads à l'aide de la fonction wait() et notify().

43
Alexander Mills

Le verrouillage consiste à protéger les données partagées.

Le verrou se trouve sur la structure de données à protéger. Les threads sont les éléments qui accèdent à la structure de données. Les verrous se trouvent sur l'objet de structure de données afin d'empêcher les threads d'accéder à la structure de données de manière non sécurisée.

Tout objet peut être utilisé comme un verrou intrinsèque (ce qui signifie utilisé en conjonction avec synchronized). De cette façon, vous pouvez protéger l'accès à n'importe quel objet en ajoutant le modificateur synchronisé aux méthodes qui accèdent aux données partagées.

Les méthodes wait et notify sont appelées sur les objets utilisés comme verrous. La serrure est un point de communication partagé:

  • Lorsqu'un thread qui a un verrou appelle notifyAll, les autres threads en attente sur ce même verrou sont avertis. Lorsqu'un thread doté d'un verrou appelle notify, l'un des threads en attente sur ce même verrou est averti.

  • Lorsqu'un thread qui a un verrou appelle wait, le thread libère le verrou et reste inactif jusqu'à ce que a) il reçoive une notification, ou b) il se réveille simplement arbitrairement (le "réveil parasite"); le thread en attente reste bloqué dans l'appel pour attendre qu'il se réveille pour l'une de ces 2 raisons, puis le thread doit réacquérir le verrou avant de pouvoir quitter la méthode d'attente.

Voir le tutoriel Oracle sur les blocs gardés , la classe Drop est la structure de données partagée, les threads utilisant les runnables Producer et Consumer y accèdent. Le verrouillage de l'objet Drop contrôle la façon dont les threads accèdent aux données de l'objet Drop.

Les threads sont utilisés comme verrous dans l'implémentation JVM, il est conseillé aux développeurs d'applications d'éviter d'utiliser des threads comme verrous. Par exemple, la documentation pour Thread.join dit:

Cette implémentation utilise une boucle d'appels this.wait conditionnée à this.isAlive. Lorsqu'un thread se termine, la méthode this.notifyAll est invoquée. Il est recommandé que les applications n'utilisent pas les instances wait, notify ou notifyAll on Thread.

Java 5 a introduit des verrous explicites implémentant Java.util.concurrent.locks.Lock. Ils sont plus flexibles que les verrous implicites; il existe des méthodes analogues pour attendre et notifier (attendre et signaler), mais elles sont sur la condition, pas sur la serrure. Le fait d'avoir plusieurs conditions permet de cibler uniquement les threads en attente d'un type particulier de notification.

35
Nathan Hughes

Vous pouvez utiliser wait() et notify() pour synchroniser votre logique. Par exemple

synchronized (lock) {
    lock.wait(); // Will block until lock.notify() is called on another thread.
}

// Somewhere else...
...
synchronized (lock) {
    lock.notify(); // Will wake up lock.wait()
}

lock étant le membre de la classe Object lock = new Object();

24
Alex

Vous pouvez arrêter votre thread pour le temps que vous voulez en utilisant la méthode de classe statique Threadsleep().

public class Main {
    //some code here

    //Thre thread will sleep for 5sec.
    Thread.sleep(5000);   
}

Si vous souhaitez arrêter certains objets, vous devez appeler cette méthode dans les blocs syncronized.

public class Main {

//some code

public void waitObject(Object object) throws InterruptedException {
    synchronized(object) {
        object.wait();
    }
}

public void notifyObject(Object object) throws InterruptedException {
    synchronized(object) {
        object.notify();
    }
}

}

P.S. Je suis désolé si je comprends mal votre question (l'anglais n'est pas mon natif)

5
Petr Shypila

Pensez à utiliser un exemple réel, un toilettes. Lorsque vous souhaitez utiliser les toilettes de votre bureau, vous avez deux options pour vous assurer que personne d'autre ne viendra aux toilettes une fois que vous les utiliserez.

  1. Verrouillez la porte des toilettes pour que tout le monde sache qu'elle est utilisée par quelqu'un d'autre lorsqu'il essaie d'ouvrir la porte
  2. Allez voir chaque personne dans le bureau, verrouillez-les sur leurs chaises (ou table, ou autre), allez aux toilettes.

Quelle option prendriez-vous?

Oui, c'est pareil dans le Javaland!.

Donc, dans l'histoire ci-dessus,

  • Salle de bain = objet que vous souhaitez verrouiller (que vous seul devez utiliser)
  • Vos collègues = autres sujets que vous souhaitez garder à l'écart

Donc, tout comme dans la vraie vie, lorsque vous avez une entreprise privée, vous verrouillez cet objet. Et lorsque vous avez terminé avec cet objet, vous lâchez le verrou!.

(Oui oui !, c'est une description très simple de ce qui se passe. Bien sûr, le vrai concept est légèrement différent de cela, mais c'est un point de départ)

5

Lorsque vous mettez du code dans un bloc synchronisé:

 sychronized(lock){...}

un thread qui souhaite effectuer ce qui se trouve à l'intérieur de ce bloc acquiert d'abord un verrou sur un objet et un seul thread à la fois peut exécuter le code verrouillé sur le même objet. Tout objet peut être utilisé comme verrou, mais vous devez faire attention à choisir l'objet correspondant à la portée. Par exemple, lorsque vous avez plusieurs threads ajoutant quelque chose au compte et qu'ils ont tous du code responsable de cela dans un bloc comme:

sychronized(this){...}

alors aucune synchronisation n'a lieu car ils sont tous verrouillés sur un objet différent. À la place, vous devez utiliser un objet de compte comme verrou. Considérez maintenant que ces fils ont également une méthode pour se retirer d'un compte. Dans ce cas, une situation peut se produire lorsqu'un thread souhaitant retirer quelque chose rencontre un compte vide. Il devrait attendre jusqu'à ce qu'il y ait de l'argent et libérer le verrou sur d'autres threads pour éviter un blocage. C'est à cela que servent les méthodes d'attente et de notification. Dans cet exemple, un thread qui rencontre un compte vide libère le verrou et attend le signal d'un thread qui effectue le dépôt:

while(balance < amountToWithdraw){
    lock.wait();
}

Quand un autre thread dépose de l'argent, cela signale d'autres threads en attente sur le même verrou. (bien sûr, le code responsable des dépôts et des retraits doit être synchronisé sur le même verrou pour que cela fonctionne et pour éviter la corruption des données).

balance += amountToDeposit;
lock.signallAll;

Comme vous le voyez, les méthodes d'attente et de notification n'ont de sens qu'à l'intérieur des blocs ou des méthodes synchronisés.

4
luke657

Dans Java all Object implémente ces deux méthodes, évidemment s'il n'y a pas de moniteur ces deux méthodes sont inutiles.

3
Nicolas HENAUX

En fait, la fonction membre wait, notify ne doit pas appartenir au thread, la chose à laquelle elle doit appartenir comme nom variable de condition qui vient de fil posix . Et vous pouvez voir comment cpp enveloppe ce concept, il l'enveloppe dans un nom de classe dédié std :: condition_variable .

Je pense que cpp fait mieux l'encapsulation que Java, Java fait trop cela, il a mis le concept directement dans la classe Object, ce qui rend les gens confus au début.

2
jean
  1. Attendre et notifier n'est pas seulement des méthodes normales ou un utilitaire de synchronisation, plus que ce sont des mécanismes de communication entre deux threads en Java. Et la classe Object est un endroit correct pour les rendre disponibles pour chaque objet si ce mécanisme n'est pas disponible via un mot-clé Java comme synchronized. N'oubliez pas que synchronized et wait notify sont deux zones différentes et ne confondez pas cela ils sont identiques ou liés. Synchronisé est de fournir une exclusion mutuelle et d'assurer la sécurité des threads de Java classe comme condition de course pendant l'attente et la notification sont un mécanisme de communication entre deux threads.
  2. Les verrous sont rendus disponibles par objet, ce qui est une autre raison pour laquelle attendre et notifier est déclaré dans la classe Object plutôt que dans la classe Thread.
  3. Dans Java afin d'entrer dans la section critique du code, les threads ont besoin d'un verrou et ils attendent le verrou, ils ne savent pas quels threads détiennent le verrou à la place, ils savent juste que le verrou est maintenu par un thread et ils devraient attendre le verrouillage au lieu de savoir quel thread se trouve dans le bloc synchronisé et leur demander de libérer le verrouillage. Cette analogie correspond à l'attente et notifie le fait d'être sur la classe d'objet plutôt que le thread en Java.

Analogie: a Java thread est un utilisateur et les toilettes sont un bloc de code que le thread souhaite exécuter. Java fournit un moyen de verrouiller le code d'un thread qui l'exécute actuellement en utilisant le keywokd synchronisé et en faisant attendre les autres threads qui souhaitent l'utiliser jusqu'à la fin du premier thread. Ces autres threads sont placés dans l'état d'attente. Java n'est pas aussi juste que la station de service car il n'y a pas de file d'attente pour les threads en attente. N'importe lequel des threads en attente peut obtenir le moniteur suivant, quel que soit l'ordre ils l'ont demandé. La seule garantie est que tous les threads pourront utiliser le code surveillé tôt ou tard.

Source

Si vous regardez le code producteur et consommateur suivant:
sharedQueue L'objet agit sur la communication inter-thread entre producer and consumer threads.

import Java.util.Vector;
import Java.util.logging.Level;
import Java.util.logging.Logger;

public class ProducerConsumerSolution {

    public static void main(String args[]) {
        Vector<Integer> sharedQueue = new Vector<Integer>();
        int size = 4;
        Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer");
        Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer");
        prodThread.start();
        consThread.start();
    }
}

class Producer implements Runnable {

    private final Vector<Integer> sharedQueue;
    private final int SIZE;

    public Producer(Vector<Integer> sharedQueue, int size) {
        this.sharedQueue = sharedQueue;
        this.SIZE = size;
    }

    @Override
    public void run() {
        for (int i = 0; i < 7; i++) {
            System.out.println("Produced: " + i);
            try {
                produce(i);
            } catch (InterruptedException ex) {
                Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    private void produce(int i) throws InterruptedException {

        // wait if queue is full
        while (sharedQueue.size() == SIZE) {
            synchronized (sharedQueue) {
                System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: "
                        + sharedQueue.size());

                sharedQueue.wait();
            }
        }

        // producing element and notify consumers
        synchronized (sharedQueue) {
            sharedQueue.add(i);
            sharedQueue.notifyAll();
        }
    }
}

class Consumer implements Runnable {

    private final Vector<Integer> sharedQueue;
    private final int SIZE;

    public Consumer(Vector<Integer> sharedQueue, int size) {
        this.sharedQueue = sharedQueue;
        this.SIZE = size;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("Consumed: " + consume());
                Thread.sleep(50);
            } catch (InterruptedException ex) {
                Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    private int consume() throws InterruptedException {
        //wait if queue is empty
        while (sharedQueue.isEmpty()) {
            synchronized (sharedQueue) {
                System.out.println("Queue is empty " + Thread.currentThread().getName()
                                    + " is waiting , size: " + sharedQueue.size());

                sharedQueue.wait();
            }
        }

        //Otherwise consume element and notify waiting producer
        synchronized (sharedQueue) {
            sharedQueue.notifyAll();
            return (Integer) sharedQueue.remove(0);
        }
    }
}

Source

2
Premraj

"Cette méthode ne doit être appelée que par un thread propriétaire du moniteur de cet objet." Je pense donc que vous devez vous assurer qu'il y a un fil qui est le moniteur sur l'objet.

1
chestre