Antworten:
Die Methoden wait()
und notify()
sollen einen Mechanismus bereitstellen, mit dem ein Thread blockieren kann, bis eine bestimmte Bedingung erfüllt ist. Ich gehe davon aus, dass Sie eine Implementierung für eine blockierende Warteschlange schreiben möchten, in der Sie einen Backing-Store mit Elementen fester Größe haben.
Das erste, was Sie tun müssen, ist, die Bedingungen zu identifizieren, auf die die Methoden warten sollen. In diesem Fall soll die put()
Methode blockiert werden, bis freier Speicherplatz im Speicher vorhanden ist, und die take()
Methode soll blockiert werden, bis ein Element zurückgegeben werden muss.
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;
}
}
Es gibt einige Dinge zu beachten, wie Sie die Warte- und Benachrichtigungsmechanismen verwenden müssen.
Zunächst müssen Sie sicherstellen, dass alle Aufrufe an wait()
oder notify()
innerhalb eines synchronisierten Codebereichs (wobei die wait()
und notify()
-Aufrufe für dasselbe Objekt synchronisiert werden). Der Grund dafür (abgesehen von den Standardbedenken hinsichtlich der Thread-Sicherheit) liegt in einem sogenannten Fehlensignal.
Ein Beispiel hierfür ist, dass ein Thread möglicherweise aufruft, put()
wenn die Warteschlange voll ist. Er überprüft dann die Bedingung und stellt fest, dass die Warteschlange voll ist. Bevor er jedoch einen anderen Thread blockieren kann, ist geplant. Dieser zweite Thread ist dann take()
ein Element aus der Warteschlange und benachrichtigt die wartenden Threads, dass die Warteschlange nicht mehr voll ist. Da der erste Thread die Bedingung jedoch bereits überprüft hat, wird er wait()
nach dem erneuten Planen einfach aufgerufen , obwohl er Fortschritte erzielen könnte.
Durch die Synchronisierung auf einem freigegebenen Objekt können Sie sicherstellen, dass dieses Problem nicht auftritt, da der take()
Aufruf des zweiten Threads erst dann Fortschritte erzielen kann, wenn der erste Thread tatsächlich blockiert wurde.
Zweitens müssen Sie die Bedingung, die Sie überprüfen, in eine while-Schleife und nicht in eine if-Anweisung einfügen, da dies als falsches Aufwecken bezeichnet wird. Hier kann ein wartender Thread manchmal wieder aktiviert werden, ohne notify()
aufgerufen zu werden. Wenn Sie diese Prüfung in eine while-Schleife einfügen, wird sichergestellt, dass bei einem falschen Aufwecken die Bedingung erneut überprüft wird und der Thread wait()
erneut aufgerufen wird.
Wie einige der anderen Antworten bereits erwähnt haben, hat Java 1.5 eine neue Parallelitätsbibliothek (im java.util.concurrent
Paket) eingeführt, die eine übergeordnete Abstraktion über den Warte- / Benachrichtigungsmechanismus bietet. Mit diesen neuen Funktionen können Sie das ursprüngliche Beispiel folgendermaßen umschreiben:
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();
}
}
}
Wenn Sie tatsächlich eine Blockierungswarteschlange benötigen, sollten Sie natürlich eine Implementierung der BlockingQueue- Schnittstelle verwenden.
Für solche Dinge würde ich Java Concurrency in der Praxis sehr empfehlen , da es alles abdeckt, was Sie über Probleme und Lösungen im Zusammenhang mit Parallelität wissen möchten.
Kein Warteschlangenbeispiel, aber extrem einfach :)
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();
}
}
}
Einige wichtige Punkte:
1) NIEMALS tun
if(!pizzaArrived){
wait();
}
Immer verwenden während (Bedingung), weil
while(!pizzaExists){ wait(); }
.2) Sie müssen die Sperre (synchronisiert) gedrückt halten, bevor Sie wait / nofity aufrufen. Die Fäden müssen vor dem Aufwachen ebenfalls blockiert werden.
3) Vermeiden Sie es, eine Sperre innerhalb Ihres synchronisierten Blocks zu erlangen, und versuchen Sie, keine außerirdischen Methoden aufzurufen (Methoden, die Sie nicht genau kennen). Wenn Sie müssen, stellen Sie sicher, dass Sie Maßnahmen ergreifen, um Deadlocks zu vermeiden.
4) Seien Sie vorsichtig mit notify (). Bleiben Sie bei notifyAll (), bis Sie wissen, was Sie tun.
5) Lesen Sie zu guter Letzt Java Concurrency in der Praxis !
pizzaArrived
Flagge benutzen ? Wenn das Flag ohne Aufruf geändert notify
wird, hat dies keine Auswirkung. Auch nur mit wait
und notify
ruft das Beispiel auf.
synchronized
Schlüsselwort geschützt ist , ist es redundant, die Variable zu deklarieren volatile
, und es wird empfohlen, sie zu vermeiden, um Verwirrung zu vermeiden. @Mrida
Auch wenn Sie nach wait()
und notify()
speziell gefragt haben , halte ich dieses Zitat für wichtig genug:
Josh Bloch, Effective Java 2nd Edition , Punkt 69: Bevorzugen Sie Parallelitätsdienstprogramme gegenüber wait
und notify
(Hervorhebung seiner):
Angesichts der Schwierigkeit der Verwendung
wait
undnotify
korrekten Verwendung sollten Sie stattdessen die übergeordneten Parallelitätsdienstprogramme verwenden, die [...] verwendenwait
undnotify
direkt der Programmierung in der "Parallelitätsassemblersprache" entsprechen, im Vergleich zu der übergeordneten Sprache, die von bereitgestellt wirdjava.util.concurrent
. Es ist selten, wenn überhaupt, Grund zu der Verwendungwait
undnotify
in neuem Code .
notify()
und das nie wait()
wieder
Haben Sie sich dieses Java-Tutorial angesehen ?
Außerdem würde ich Ihnen raten, sich nicht mit solchen Dingen in echter Software zu beschäftigen. Es ist gut, damit zu spielen, damit Sie wissen, was es ist, aber Parallelität hat überall Fallstricke. Es ist besser, übergeordnete Abstraktionen und synchronisierte Sammlungen oder JMS-Warteschlangen zu verwenden, wenn Sie Software für andere Personen erstellen.
Das ist zumindest was ich tue. Ich bin kein Experte für Parallelität, daher halte ich mich nach Möglichkeit davon fern, Threads von Hand zu behandeln.
Beispiel
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
}
}
Beispiel für wait () und notifyall () in Threading.
Eine synchronisierte statische Array-Liste wird als Ressource verwendet, und die wait () -Methode wird aufgerufen, wenn die Array-Liste leer ist. Die notify () -Methode wird aufgerufen, sobald ein Element für die Array-Liste hinzugefügt wird.
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)
, ich denke, es könnte ein Fehler hier sein.
notify
weckt nur einen Thread. Wenn zwei Consumer-Threads miteinander konkurrieren, um ein Element zu entfernen, weckt eine Benachrichtigung möglicherweise den anderen Consumer-Thread, der nichts dagegen tun kann und wieder in den Ruhezustand wechselt (anstelle des Produzenten, von dem wir gehofft hatten, dass er ein neues Element einfügt.) Weil Der Producer-Thread wird nicht geweckt, es wird nichts eingefügt und jetzt werden alle drei Threads auf unbestimmte Zeit schlafen. Ich entfernte meinen vorherigen Kommentar, als er (fälschlicherweise) sagte, dass falsches Aufwachen die Ursache des Problems war (ist es nicht.)