Puis-je obtenir un scénario simple et complet, par exemple un didacticiel, suggérant comment l'utiliser, en particulier avec une file d'attente?
Les méthodes wait()
et notify()
sont conçues pour fournir un mécanisme permettant à un thread de bloquer jusqu'à ce qu'une condition spécifique soit remplie. Pour cela, je suppose que vous souhaitez écrire une implémentation de la file d'attente de blocage, dans laquelle vous avez un magasin de sauvegarde d'éléments de taille fixe.
La première chose à faire est d’identifier les conditions que vous souhaitez que les méthodes attendent. Dans ce cas, vous voudrez que la méthode put()
bloque jusqu'à ce qu'il y ait de l'espace libre dans le magasin, et vous voudrez que la méthode take()
bloque jusqu'à ce qu'un élément soit renvoyé.
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void put(T element) throws InterruptedException {
while(queue.size() == capacity) {
wait();
}
queue.add(element);
notify(); // notifyAll() for multiple producer/consumer threads
}
public synchronized T take() throws InterruptedException {
while(queue.isEmpty()) {
wait();
}
T item = queue.remove();
notify(); // notifyAll() for multiple producer/consumer threads
return item;
}
}
Il y a quelques points à noter sur la manière dont vous devez utiliser les mécanismes d'attente et de notification.
Tout d'abord, vous devez vous assurer que tous les appels à wait()
ou notify()
se trouvent dans une région de code synchronisée (les appels wait()
et notify()
étant synchronisés sur le même objet) . La raison à cela (autre que les problèmes de sécurité du thread standard) est due à quelque chose appelé un signal manqué.
Un exemple de ceci est qu'un thread peut appeler put()
lorsque la file d'attente est saturée, il vérifie ensuite la condition, vérifie que la file est saturée, mais avant de pouvoir bloquer un autre thread programmé. Ce deuxième thread est alors un élément de la file d'attente take()
et informe les threads en attente que la file d'attente n'est plus saturée. Cependant, comme le premier thread a déjà vérifié la condition, il appellera simplement wait()
après avoir été reprogrammé, même s'il pourrait progresser.
En effectuant une synchronisation sur un objet partagé, vous pouvez vous assurer que ce problème ne se produira pas, car l'appel de la fonction take()
du deuxième thread ne pourra pas progresser tant que le premier thread ne sera pas réellement bloqué.
Deuxièmement, vous devez mettre la condition que vous vérifiez dans une boucle while, plutôt que dans une instruction if, en raison d'un problème appelé réveils parasites. C'est là qu'un thread en attente peut parfois être réactivé sans que notify()
ne soit appelé. Mettre cette vérification dans une boucle while garantira que si un réveil parasite se produit, la condition est vérifiée à nouveau et le thread appelle à nouveau wait()
.
Comme certaines des autres réponses l'ont mentionné, Java 1.5 a introduit une nouvelle bibliothèque de concurrence (dans le package Java.util.concurrent
) conçue pour fournir une abstraction de niveau supérieur par rapport au mécanisme attente/notification. En utilisant ces nouvelles fonctionnalités, vous pouvez réécrire l’exemple original de la manière suivante:
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public void put(T element) throws InterruptedException {
lock.lock();
try {
while(queue.size() == capacity) {
notFull.await();
}
queue.add(element);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while(queue.isEmpty()) {
notEmpty.await();
}
T item = queue.remove();
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
Bien sûr, si vous avez réellement besoin d'une file d'attente de blocage, vous devez utiliser une implémentation de l'interface BlockingQueue .
En outre, je recommanderais fortement Java Concurrency in Practice , car il couvre tout ce que vous pourriez vouloir savoir sur les problèmes et solutions liés à la simultanéité.
Pas un exemple de file d'attente, mais extrêmement simple :)
class MyHouse {
private boolean pizzaArrived = false;
public void eatPizza(){
synchronized(this){
while(!pizzaArrived){
wait();
}
}
System.out.println("yumyum..");
}
public void pizzaGuy(){
synchronized(this){
this.pizzaArrived = true;
notifyAll();
}
}
}
Quelques points importants:
1) Ne faites JAMAIS
if(!pizzaArrived){
wait();
}
Toujours utiliser while (condition), car
while(!pizzaExists){ wait(); }
.2) Vous devez maintenir le verrou (synchronisé) avant d'appeler wait/nofity. Les fils doivent également acquérir un verrou avant le réveil.
3) Essayez d'éviter d'acquérir un verrou dans votre bloc synchronisé et efforcez-vous de ne pas invoquer de méthodes extraterrestres (méthodes que vous ne savez pas vraiment ce qu'elles font). Si vous devez le faire, assurez-vous de prendre des mesures pour éviter les blocages.
4) Soyez prudent avec notify (). Restez avec notifyAll () jusqu'à ce que vous sachiez ce que vous faites.
5) Dernier point, mais non des moindres, lisez La concurrence de Java en pratique !
Même si vous avez demandé spécifiquement wait()
et notify()
, je pense que cette citation est encore assez importante:
Josh Bloch, En vigueur Java 2e édition , élément 69: Préférez les utilitaires d'accès concurrent à wait
et notify
(souligné par son):
Étant donné la difficulté d'utiliser correctement
wait
etnotify
, vous devez utiliser les utilitaires de concurrence de niveau supérieur à la place [...] utilisantwait
etnotify
est directement similaire à la programmation en "langage simultané d'assemblage", par rapport au langage de niveau supérieur fourni parJava.util.concurrent
. Il y a rarement, si jamais, une raison d'utiliserwait
etnotify
dans le nouveau code.
Avez-vous jeté un coup d'oeil à ceci Tutoriel Java ?
De plus, je vous conseillerais de ne pas trop jouer avec ce genre de choses dans de vrais logiciels. C'est bien de jouer avec cela pour savoir ce que c'est, mais la concurrence présente des pièges partout. Il est préférable d'utiliser des abstractions de niveau supérieur et des collections synchronisées ou des files d'attente JMS si vous construisez un logiciel pour d'autres personnes.
C'est du moins ce que je fais. Je ne suis pas un expert en concurrence, donc je m'éloigne de la manipulation manuelle des threads dans la mesure du possible.
Exemple
public class myThread extends Thread{
@override
public void run(){
while(true){
threadCondWait();// Circle waiting...
//bla bla bla bla
}
}
public synchronized void threadCondWait(){
while(myCondition){
wait();//Comminucate with notify()
}
}
}
public class myAnotherThread extends Thread{
@override
public void run(){
//Bla Bla bla
notify();//Trigger wait() Next Step
}
}
Exemple pour wait () et notifyall () dans Threading.
Une liste de tableaux statiques synchronisés est utilisée en tant que ressource et la méthode wait () est appelée si la liste de tableaux est vide. La méthode notify () est appelée une fois qu'un élément est ajouté à la liste de tableaux.
public class PrinterResource extends Thread{
//resource
public static List<String> arrayList = new ArrayList<String>();
public void addElement(String a){
//System.out.println("Add element method "+this.getName());
synchronized (arrayList) {
arrayList.add(a);
arrayList.notifyAll();
}
}
public void removeElement(){
//System.out.println("Remove element method "+this.getName());
synchronized (arrayList) {
if(arrayList.size() == 0){
try {
arrayList.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
arrayList.remove(0);
}
}
}
public void run(){
System.out.println("Thread name -- "+this.getName());
if(!this.getName().equalsIgnoreCase("p4")){
this.removeElement();
}
this.addElement("threads");
}
public static void main(String[] args) {
PrinterResource p1 = new PrinterResource();
p1.setName("p1");
p1.start();
PrinterResource p2 = new PrinterResource();
p2.setName("p2");
p2.start();
PrinterResource p3 = new PrinterResource();
p3.setName("p3");
p3.start();
PrinterResource p4 = new PrinterResource();
p4.setName("p4");
p4.start();
try{
p1.join();
p2.join();
p3.join();
p4.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Final size of arraylist "+arrayList.size());
}
}