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()
.
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.
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();
Vous pouvez arrêter votre thread pour le temps que vous voulez en utilisant la méthode de classe statique Thread
sleep()
.
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)
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.
Quelle option prendriez-vous?
Oui, c'est pareil dans le Javaland!.
Donc, dans l'histoire ci-dessus,
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)
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.
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.
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.
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.
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);
}
}
}
"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.