Heim >Java >javaLernprogramm >Korrekte Verwendung von „wait', „notify' und „notifyAll'.

Korrekte Verwendung von „wait', „notify' und „notifyAll'.

阿神
阿神Original
2017-03-18 10:09:042172Durchsuche

In Java können Sie wait, notify und notifyAll verwenden, um die Kommunikation zwischen Threads zu implementieren. . Wenn Sie beispielsweise zwei Threads in Ihrem Java-Programm haben – einen Produzenten und einen Konsumenten –, kann der Produzent den Konsumenten benachrichtigen, mit dem Konsumieren von Daten zu beginnen, da im Warteschlangenpuffer Inhalt zum Konsumieren vorhanden ist (nicht für Null). Dementsprechend kann der Verbraucher den Produzenten benachrichtigen, dass er mit der Generierung weiterer Daten beginnen kann, da der Puffer nicht mehr voll ist, nachdem er einige Daten verbraucht hat.

wait, notify und notifyAll, diese reservierten Schlüsselwörter, die häufig beim Multithreading verwendet werden, werden während der tatsächlichen Entwicklung oft nicht ernst genommen. In diesem Artikel wird die Verwendung dieser Schlüsselwörter beschrieben.

In Java können Sie Wait, Notify und NotifyAll verwenden, um die Kommunikation zwischen Threads zu erreichen. . Wenn Sie beispielsweise zwei Threads in Ihrem Java-Programm haben – einen Produzenten und einen Konsumenten –, kann der Produzent den Konsumenten benachrichtigen, mit dem Konsumieren von Daten zu beginnen, da im Warteschlangenpuffer Inhalt zum Konsumieren vorhanden ist (nicht für Null). Dementsprechend kann der Verbraucher den Produzenten benachrichtigen, dass er mit der Generierung weiterer Daten beginnen kann, da der Puffer nicht mehr voll ist, nachdem er einige Daten verbraucht hat.

Wir können wait() verwenden, um einen Thread unter bestimmten Bedingungen anzuhalten. Im Producer-Consumer-Modell sollte der Producer-Thread beispielsweise anhalten, wenn der Puffer voll und der Consumer-Thread leer ist. Wenn einige Threads darauf warten, dass bestimmte Bedingungen ausgelöst werden, können Sie, wenn diese Bedingungen erfüllt sind, notify und notifyAll verwenden, um die wartenden Threads zu benachrichtigen, damit sie erneut mit der Ausführung beginnen. Der Unterschied besteht darin, dass notify nur einen Thread benachrichtigt und wir nicht wissen, welcher Thread die Benachrichtigung erhält, während notifyAll alle wartenden Threads benachrichtigt. Mit anderen Worten: Wenn nur ein Thread auf ein Semaphor wartet, benachrichtigen sowohl notify als auch notifyAll diesen Thread. Wenn jedoch mehrere Threads auf dieses Semaphor warten, benachrichtigt notify nur einen von ihnen, andere Threads erhalten keine Benachrichtigung und notifyAll weckt alle wartenden Threads.

In diesem Artikel erfahren Sie, wie Sie Wait, Notify und NotifyAll verwenden, um die Kommunikation zwischen Threads zu implementieren und das Producer-Consumer-Problem zu lösen. Wenn Sie mehr über Multithread-Synchronisierungsprobleme in Java erfahren möchten, empfehle ich dringend die Lektüre von „Java Concurrency in Practice | Java Concurrency Practice“ von Brian Goetz. Ihre Java-Multithreading-Reise ist unvollständig, ohne dieses Buch zu lesen. Dies ist eines meiner am meisten empfohlenen Bücher für Java-Entwickler.

So verwenden Sie Wait

Obwohl die Konzepte von Wait und Notify sehr einfach sind und beide Funktionen der Object-Klasse sind, ist es nicht einfach, damit Code zu schreiben ihnen. . Wenn Sie Kandidaten bitten, während des Vorstellungsgesprächs Code von Hand zu schreiben und Warten und Benachrichtigen zu verwenden, um Producer-Consumer-Probleme zu lösen, bin ich mir fast sicher, dass die meisten von ihnen ratlos sein oder einige Fehler machen werden, wie zum Beispiel die falsche Verwendung des synchronisierten Schlüsselworts Nicht das richtige Objekt verwenden oder nicht dem Standardcode-Ansatz folgen. Ehrlich gesagt bereitet dieses Problem tatsächlich Kopfschmerzen für Programmierer, die sie nicht oft verwenden.

Die erste Frage ist: Wie verwenden wir wait() im Code? Da wait() keine Funktion der Thread-Klasse ist, können wir Thread.call() nicht verwenden. Tatsächlich schreiben viele Java-Programmierer gerne so, weil sie es gewohnt sind, Thread.sleep () zu verwenden. Daher werden sie versuchen, wait () zu verwenden, um denselben Zweck zu erreichen. Sie werden jedoch bald feststellen, dass dies das Problem nicht löst Problem reibungslos. Die richtige Methode besteht darin, auf das von mehreren Threads gemeinsam genutzte Objekt zu warten. Im Producer-Consumer-Problem ist dieses gemeinsam genutzte Objekt die Pufferwarteschlange.

Die zweite Frage ist: Welches Objekt sollte synchronisiert werden, da wir „wait“ in einer synchronisierten Funktion oder einem synchronisierten Objekt aufrufen sollten? Die Antwort ist, dass das Objekt, das Sie sperren möchten, synchronisiert sein sollte, also das Objekt, das von mehreren Threads gemeinsam genutzt wird. Beim Producer-Consumer-Problem sollte die Pufferwarteschlange synchronisiert werden. (Ich glaube, mit dem englischen Originaltext stimmt etwas nicht... Am Ende des Satzes sollte kein Fragezeichen stehen, sonst ergibt er keinen Sinn...)

如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

wird immer in einer Warte- und Benachrichtigungsschleife aufgerufen, nicht in der If-Anweisung

现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在 while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等 待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤 醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者 开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!我会推荐阅读《Effective Java》,这是关于如何正确使用wait和notify的最好的参考资料。

基于以上认知,下面这个是使用wait和notify函数的规范代码模板:

// The standard idiom for calling the wait method in Java 
synchronized (sharedObject) { 
    while (condition) { 
sharedObject.wait(); 
        // (Releases lock, and reacquires on wakeup) 
    } 
    // do action based upon condition e.g. take or put into queue 
}

就像我之前说的一样,在while循环里使用wait的目的,是在线程被唤醒的前后都持续检查条件是否被满足。如果条件并未改变,wait被调用之前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。

Java wait(), notify(), notifyAll() 范例

下面我们提供一个使用wait和notify的范例程序。在这个程序里,我们使用了上文所述的一些代码规范。我们有两个线程,分别名为 PRODUCER(生产者)和CONSUMER(消费者),他们分别继承了了Producer和Consumer类,而Producer和 Consumer都继承了Thread类。Producer和Consumer想要实现的代码逻辑都在run()函数内。Main线程开始了生产者和消费 者线程,并声明了一个LinkedList作为缓冲区队列(在Java中,LinkedList实现了队列的接口)。生产者在无限循环中持续往 LinkedList里插入随机整数直到LinkedList满。我们在while(queue.size ==  maxSize)循环语句中检查这个条件。请注意到我们在做这个检查条件之前已经在队列对象上使用了synchronized关键词,因而其它线程不能在 我们检查条件时改变这个队列。如果队列满了,那么PRODUCER线程会在CONSUMER线程消耗掉队列里的任意一个整数,并用notify来通知 PRODUCER线程之前持续等待。在我们的例子中,wait和notify都是使用在同一个共享对象上的。

import java.util.LinkedList; 
import java.util.Queue; 
import java.util.Random; 
/** 
* Simple Java program to demonstrate How to use wait, notify and notifyAll() 
* method in Java by solving producer consumer problem. 
* 
* @author Javin Paul 
*/ 
public class ProducerConsumerInJava { 
public static void main(String args[]) { 
  System.out.println("How to use wait and notify method in Java"); 
  System.out.println("Solving Producer Consumper Problem"); 
  Queue<Integer> buffer = new LinkedList<>(); 
  int maxSize = 10; 
  Thread producer = new Producer(buffer, maxSize, "PRODUCER"); 
  Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); 
  producer.start(); consumer.start(); } 
} 
/** 
* Producer Thread will keep producing values for Consumer 
* to consumer. It will use wait() method when Queue is full 
* and use notify() method to send notification to Consumer 
* Thread. 
* 
* @author WINDOWS 8 
* 
*/ 
class Producer extends Thread 
{ private Queue<Integer> queue; 
  private int maxSize; 
  public Producer(Queue<Integer> queue, int maxSize, String name){ 
   super(name); this.queue = queue; this.maxSize = maxSize; 
  } 
  @Override public void run() 
  { 
   while (true) 
    { 
     synchronized (queue) { 
      while (queue.size() == maxSize) { 
       try { 
        System.out .println("Queue is full, " + "Producer thread waiting for " + "consumer to take something from queue"); 
        queue.wait(); 
       } catch (Exception ex) { 
        ex.printStackTrace(); } 
       } 
       Random random = new Random(); 
       int i = random.nextInt(); 
       System.out.println("Producing value : " + i); queue.add(i); queue.notifyAll(); 
      } 
     } 
    } 
   } 
/** 
* Consumer Thread will consumer values form shared queue. 
* It will also use wait() method to wait if queue is 
* empty. It will also use notify method to send 
* notification to producer thread after consuming values 
* from queue. 
* 
* @author WINDOWS 8 
* 
*/ 
class Consumer extends Thread { 
  private Queue<Integer> queue; 
  private int maxSize; 
  public Consumer(Queue<Integer> queue, int maxSize, String name){ 
   super(name); 
   this.queue = queue; 
   this.maxSize = maxSize; 
  } 
  @Override public void run() { 
   while (true) { 
    synchronized (queue) { 
     while (queue.isEmpty()) { 
      System.out.println("Queue is empty," + "Consumer thread is waiting" + " for producer thread to put something in queue"); 
      try { 
       queue.wait(); 
      } catch (Exception ex) { 
       ex.printStackTrace(); 
      } 
     } 
     System.out.println("Consuming value : " + queue.remove()); queue.notifyAll(); 
    } 
   } 
  } 
}

如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

为了更好地理解这个程序,我建议你在debug模式里跑这个程序。一旦你在debug模式下启动程序,它会停止在PRODUCER或者 CONSUMER线程上,取决于哪个线程占据了CPU。因为两个线程都有wait()的条件,它们一定会停止,然后你就可以跑这个程序然后看发生什么了 (很有可能它就会输出我们以上展示的内容)。你也可以使用Eclipse里的Step into和Step over按钮来更好地理解多线程间发生的事情。

本文重点:

1. 你可以使用wait和notify函数来实现线程间通信。你可以用它们来实现多线程(>3)之间的通信。

2. 永远在synchronized的函数或对象里使用wait、notify和notifyAll,不然Java虚拟机会生成 IllegalMonitorStateException。

3. 永远在while循环里而不是if语句下使用wait。这样,循环会在线程睡眠前后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知。

4. 永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用wait。

5. 基于前文提及的理由,更倾向用 notifyAll(),而不是 notify()。

如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

这是关于Java里如何使用wait,  notify和notifyAll的所有重点啦。你应该只在你知道自己要做什么的情况下使用这些函数,不然Java里还有很多其它的用来解决同步问题的方 案。例如,如果你想使用生产者消费者模型的话,你也可以使用BlockingQueue,它会帮你处理所有的线程安全问题和流程控制。如果你想要某一个线 程等待另一个线程做出反馈再继续运行,你也可以使用CycliBarrier或者CountDownLatch。如果你只是想保护某一个资源的话,你也可 以使用Semaphore。

相关文章:

Eine detaillierte Einführung in den Vergleich zwischen Java Notify und NotifyAll

Zwei Arten der Zusammenarbeit zwischen Threads in der Java-Parallelität: Warten, Benachrichtigen, NotifyAll und Bedingung

Besprechen Sie den wesentlichen Unterschied zwischen notify() und notifyAll() anhand von Beispielen

Das obige ist der detaillierte Inhalt vonKorrekte Verwendung von „wait', „notify' und „notifyAll'.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn