Réponses:
Le wait()
etnotify()
méthodes sont conçues pour fournir un mécanisme permettant à un thread de se bloquer jusqu'à ce qu'une condition spécifique soit remplie. Pour cela, je suppose que vous souhaitez écrire une implémentation de file d'attente de blocage, où vous avez un stockage 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 souhaiterez que la put()
méthode se bloque jusqu'à ce qu'il y ait de l'espace libre dans le magasin, et vous souhaiterez que la take()
méthode se bloque jusqu'à ce qu'il y ait un élément à retourner.
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 vers wait()
ou se notify()
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é des threads standard) est due à quelque chose que l'on appelle un signal manqué.
Un exemple de ceci est qu'un thread peut appeler put()
lorsque la file d'attente est pleine, il vérifie ensuite la condition, voit que la file d'attente est pleine, mais avant de pouvoir bloquer un autre thread est planifié. Ce deuxième thread est alors take()
un élément de la file d'attente et notifie aux threads en attente que la file d'attente n'est plus pleine. Cependant, comme le premier thread a déjà vérifié la condition, il appellera simplementwait()
après avoir été replanifié, même s'il pourrait progresser.
En synchronisant sur un objet partagé, vous pouvez vous assurer que ce problème ne se produit pas, car le deuxième thread take()
appel ne pourra pas progresser tant que le premier thread n'aura pas été bloqué.
Deuxièmement, vous devez placer la condition que vous vérifiez dans une boucle while, plutôt qu'une instruction if, en raison d'un problème connu sous le nom de faux réveils. C'est là qu'un thread en attente peut parfois être réactivé sans notify()
être appelé. Mettre cette vérification dans une boucle while garantira que si un faux réveil se produit, la condition sera revérifiée et le thread appellerawait()
nouveau.
Comme certaines des autres réponses l'ont mentionné, Java 1.5 a introduit une nouvelle bibliothèque de concurrence (dans le java.util.concurrent
package) qui a été conçue pour fournir une abstraction de plus haut niveau sur le mécanisme d'attente / notification. En utilisant ces nouvelles fonctionnalités, vous pouvez réécrire l'exemple d'origine comme ceci:
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, pour des choses comme celle-ci, je recommande vivement Java Concurrency en pratique , car il couvre tout ce que vous pourriez vouloir savoir sur les problèmes et les solutions liés à la concurrence.
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 JAMAIS faire
if(!pizzaArrived){
wait();
}
Utilisez toujours while (condition), car
while(!pizzaExists){ wait(); }
.2) Vous devez maintenir le verrou (synchronisé) avant d'appeler wait / nofity. Les threads doivent également acquérir un verrouillage avant de se réveiller.
3) Essayez d'éviter d'acquérir un verrou dans votre bloc synchronisé et efforcez-vous de ne pas invoquer de méthodes étrangères (méthodes dont vous ne savez pas avec certitude ce qu'elles font). Si nécessaire, 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 le moindre, lisez Java Concurrency en pratique !
pizzaArrived
drapeau? si le drapeau est changé sans un appel, notify
il n'aura aucun effet. Aussi juste avec wait
et notify
appelle l'exemple fonctionne.
synchronized
mot-clé, il est redondant de déclarer la variable volatile
, et il est recommandé de l'éviter pour éviter toute confusion @mrida
Même si vous avez demandé wait()
et notify()
spécifiquement, je pense que cette citation est encore assez importante:
Josh Bloch, Effective Java 2nd Edition , Item 69: Préférez les utilitaires de concurrence à wait
et notify
(soulignement):
Étant donné la difficulté à utiliser
wait
etnotify
correctement, vous devriez utiliser les utilitaires de concurrence de plus haut niveau à la place [...] d'utiliserwait
et c'estnotify
directement comme la programmation en "langage d'assemblage de concurrence", par rapport au langage de plus haut niveau fourni parjava.util.concurrent
. Il y a rarement, voire jamais, de raison d'utiliserwait
etnotify
dans un nouveau code .
notify()
et le wait()
nouveau
Avez-vous jeté un œil à ce didacticiel Java ?
De plus, je vous conseillerais de ne pas jouer avec ce genre de choses dans de vrais logiciels. Il est bon de jouer avec pour savoir ce que c'est, mais la concurrence a 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 créez des logiciels pour d'autres personnes.
C'est du moins ce que je fais. Je ne suis pas un expert de la concurrence, donc je reste loin de gérer les threads à la main 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 comme 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é pour 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());
}
}
if(arrayList.size() == 0)
, je pense que cela pourrait être une erreur ici.
notify
ne réveille qu'un seul thread. Si deux threads consommateurs sont en concurrence pour supprimer un élément, une notification peut réveiller l'autre thread consommateur, qui ne peut rien y faire et va se rendormir (au lieu du producteur, qui nous espérions insérer un nouvel élément.) Parce que le thread producteur n'est pas réveillé, rien n'est inséré et maintenant les trois threads vont dormir indéfiniment. J'ai supprimé mon commentaire précédent car il disait (à tort) qu'un faux réveil était la cause du problème (ce n'est pas le cas.)