Ich habe versucht, etwas Ähnliches wie Java zu implementieren. BlockingQueue Schnittstelle mit Java-Synchronisation "primitives" (synchronisiert, wait (), notify () ), als ich auf ein Verhalten stieß, das ich nicht verstehe.
Ich erstelle eine Warteschlange, die 1 Element speichern kann, erstelle zwei Threads, die warten, um einen Wert aus der Warteschlange zu holen, starte sie und versuche dann, zwei Werte in die Warteschlange in einem synchronisierten Block im Haupt-Thread einzutragen. Die meiste Zeit funktioniert es, aber manchmal beginnen die zwei Threads, die auf einen Wert warten, sich scheinbar gegenseitig aufzuwecken und lassen den Hauptfaden nicht in den synchronisierten Block eindringen.
Hier ist mein (vereinfachter) Code:
%Vor%Folgendes sehe ich, wenn es "blockiert":
%Vor%Wenn ich jedoch die notify () -Aufrufe innerhalb der while (...) - Schleifen von addWhenHasSpace und getWhenNotEmpty-Methoden zu notifyAll () ändere, übergibt es "immer".
Meine Frage lautet: Warum variiert das Verhalten in diesem Fall zwischen den Methoden notify () und notifyAll (), und warum ist das Verhalten von notify () so wie es ist?
Ich würde erwarten, dass sich beide Methoden in diesem Fall genauso verhalten (zwei Threads WARTEN, ein BLOCKED), weil:
Oder mache ich vielleicht etwas falsch?
Es scheint eine Art von Fairness / Barging zu geben, die mit intrinsischer Verriegelung arbeitet - wahrscheinlich aufgrund einiger Optimierungen. Ich vermute, dass der native Code prüft, ob der aktuelle Thread den Monitor benachrichtigt hat, auf den er wartet, und ihn gewinnen lässt.
Ersetze synchronized
durch ReentrantLock
und es sollte so funktionieren, wie du es erwartest. Der Unterschied hier ist, wie der ReentrantLock
Kellner einer Sperre behandelt, die er benachrichtigt hat.
Aktualisierung:
Interessante finden Sie hier. Was Sie sehen, ist ein Rennen zwischen dem main
-Thread
, während die anderen beiden Threads ihre jeweiligen synchronized
-Regionen eingeben. Wenn der Hauptthread nicht vor mindestens einem der beiden in den Synchronisierungsbereich gelangt, erhalten Sie diese Live-Lock-Ausgabe, die Sie beschreiben.
Es scheint so zu sein, dass, wenn beide Consumer-Threads zuerst den Sync-Block treffen, sie für notify
und wait
miteinander pingen. Es kann der Fall sein, dass die JVM Threads gibt, die auf den Monitor warten, während Threads blockiert sind.
Ohne zu sehr auf Ihren Code einzugehen, sehe ich, dass Sie eine einzige Bedingungsvariable verwenden, um eine Warteschlange mit einem Producer und mehr als einem Consumer zu implementieren. Das ist ein Rezept für Probleme: Wenn es nur eine Bedingungsvariable gibt, kann ein Verbraucher, wenn er notify()
aufruft, nicht wissen, ob er den Producer aufweckt oder den anderen Consumer weckt.
Es gibt zwei Wege aus dieser Falle: Am einfachsten ist es, immer notifyAll().
Die andere Möglichkeit besteht darin, die Verwendung von synchronized
, wait()
und notify()
zu beenden und stattdessen die Funktionen in java.util.concurrent.locks zu verwenden.
Ein einzelnes ReentrantLock-Objekt kann Ihnen zwei (oder mehr) Zustandsvariablen geben. Verwenden Sie eine ausschließlich für den Hersteller, um die Verbraucher zu benachrichtigen, und die andere ausschließlich für die Verbraucher, um den Hersteller zu benachrichtigen.
Hinweis: Die Namen ändern sich, wenn Sie zu ReentrantLocks wechseln: o.wait()
wird c.await()
und o.notify()
wird c.signal()
.
Tags und Links java multithreading concurrency notify livelock