Java: zwei WAITING + ein BLOCKED Thread, notify () führt zu einem Livelock, notifyAll () nicht, warum?

8

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:

  1. es scheint mir, dass in diesem Fall notifyAll () nur den anderen Thread aufwecken würde, genauso wie notify ();
  2. Es sieht so aus, als ob die Wahl der Methode, die einen Thread aufweckt, beeinflusst, wie der Thread, der aufgeweckt wird (und wahrscheinlich RUNNABLE wird) und der Hauptthread (der BLOCKIERT wurde) später konkurriert für das Schloss - nicht etwas, was ich von der Javadoc erwarten würde, sowie die Suche im Internet zu diesem Thema.

Oder mache ich vielleicht etwas falsch?

    
starikoff 17.11.2014, 16:17
quelle

2 Antworten

2

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

%Vor%

, 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.

    
John Vint 17.11.2014 19:10
quelle
1

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().

zu verwenden

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() .

    
james large 17.11.2014 19:43
quelle