2017-05-06 1 views
1

Lassen Sie uns das folgende Stück Code in Java betrachtenflüchtigen vs nicht flüchtigen

int x = 0; 
int who = 1 
Thread #1: 
    (1) x++; 
    (2) who = 2; 

Thread #2 
    while(who == 1); 
    x++; 
    print x; (the value should be equal to 2 but, perhaps, it is not*)  

(ich weiß nicht, Java Speicher modell- ich meine Models- lassen vermuten, dass es starke Erinnerung ist: (1) und (2) wird nicht vertauscht)
Java-Speicher-Modell garantiert, dass Zugriff/Speicherung auf die 32-Bit-Variablen atomaren ist, so dass unser Programm sicher ist. Aber trotzdem sollten wir ein Attribut volatile wegen * verwenden. Der Wert x kann gleich 1 sein, da x im Register gespeichert werden kann, wenn Thread#2 gelesen wird. Um es zu lösen, sollten wir die x Variable volatile machen. Alles klar.

Aber, was über diese Situation:

int x = 0; 
    mutex m; (just any mutex) 
Thread #1: 
     mutex.lock() 
     x++; 
     mutex.unlock() 

    Thread #2 
     mutex.lock() 
     x++; 
     print x; // the value is always 2, why**? 
     mutex.unlock() 

Der Wert von x ist immer 2 obwohl wir es nicht volatile machen. Verstehe ich richtig, dass das Sperren/Entsperren von Mutex mit dem Einfügen von Speicherbarrieren verbunden ist?

+0

Bitte poste ein MCVE. –

+0

Was ist ein MCVE? – Gilgamesz

+1

Lies darüber [hier] (https://StackOverflow.com/Help/Mcve) –

Antwort

3

Ich werde versuchen, dies anzugehen. Das Java-Speichermodell ist in einem einzelnen StackOverflow-Post involviert und schwer zu enthalten. Bitte beziehen Sie sich auf Brian Goetz Java Concurrency in der Praxis für die ganze Geschichte.

Der Wert von x ist immer 2, obwohl wir es nicht flüchtig machen. Verstehe ich richtig, dass das Sperren/Entsperren von Mutex mit dem Einfügen von Speicherbarrieren verbunden ist?

Zuerst, wenn Sie das Java-Speichermodell verstehen möchten, ist es immer Chapter 17 of the spec, die Sie durchlesen möchten.

Das spec sagt:

ein Unlock auf einem Monitor passiert-vor jeder weiteren Sperre auf diesem Monitor.

Also ja, es gibt ein Speichersichtbarkeitsereignis beim Entsperren Ihres Monitors. (Ich gehe davon aus durch „Mutex“ Sie bedeuten überwachen. Die meisten der Schleusen und anderen Klassen im java.utils.concurrent Paket haben auch passiert-vor Semantik, die Dokumentation überprüfen.)

Happens-vor ist, was Java bedeutet, wenn Es garantiert nicht nur, dass die Ereignisse geordnet sind, sondern auch, dass die Sichtbarkeit des Speichers gewährleistet ist.

We say that a read r of a variable v is allowed to observe a write w 
to v if, in the happens-before partial order of the execution trace: 

    r is not ordered before w (i.e., it is not the case that 
    hb(r, w)), and 

    there is no intervening write w' to v (i.e. no write w' to v such 
    that hb(w, w') and hb(w', r)). 

Informally, a read r is allowed to see the result of a write w if there 
is no happens-before ordering to prevent that read. 

Dies ist alles von 17.4.5. Es ist ein wenig verwirrend zu lesen, aber die Informationen sind alle da, wenn Sie es durchlesen.

+0

@scottb Nun, er schien nach Mutex/Monitoren zu fragen. Das "volatile" war nur ein Teil seiner Prämisse. Natürlich können wir hier oft spezifische Fragen beantworten, aber es ist wirklich notwendig, das vollständige Speichermodell zu verstehen, um in Java gut zu programmieren.Zum Beispiel wissen viele Leute, was "volatile" tut, aber nur wenige scheinen zu wissen, was "final" tut. (Hinweis: lesen Sie Kapitel 17 der Spezifikation !!) – markspace

1

Lassen Sie uns über einige Dinge gehen. Die folgende Aussage ist wahr: Das Java-Speichermodell garantiert, dass der Zugriff/Speicher auf die 32-Bit-Variablen atomar ist. Es folgt jedoch nicht, dass das erste von Ihnen gelistete Pseudoprogramm sicher ist. Da zwei Anweisungen syntaktisch geordnet sind, bedeutet nicht, dass die Sichtbarkeit ihrer Aktualisierungen auch so angeordnet ist, wie sie von anderen Threads angezeigt werden. Thread # 2 kann die von wer = 2 verursachte Aktualisierung sehen, bevor das Inkrement in x sichtbar ist. X volatile zu machen, würde das Programm immer noch nicht korrekt machen. Stattdessen würde die Variable "who" volatile das Programm korrekt machen. Das liegt daran, dass volatile auf spezifische Weise mit dem Java-Speichermodell interagiert.

Ich fühle mich wie es gibt eine Vorstellung von "zurück zum Hauptspeicher schreiben" im Kern eines gesunden Menschenverstandes Verständnis von flüchtigen, die inkorrekt ist. Volatile schreibt den Wert für den Hauptspeicher in Java nicht zurück. Was man aus einer flüchtigen Variablen liest und in eine flüchtige Variable schreibt, erzeugt etwas, was man als Vor-Vor-Beziehung bezeichnet. Wenn Thread # 1 in eine flüchtige Variable schreibt, erstellen Sie eine Beziehung, die sicherstellt, dass alle anderen Threads # 2, die diese flüchtige Variable anzeigen, auch alle Aktionen sehen können, die Thread # 1 zuvor ausgeführt hat. In Ihrem Beispiel bedeutet das, "wer" volatil zu machen. Wenn Sie den Wert 2 in 'wer' schreiben, erstellen Sie eine Vorkommnis-Beziehung, so dass, wenn Thread # 2 wer = 2 anzeigt, auch eine aktualisierte Version von x angezeigt wird.

In Ihrem zweiten Beispiel (vorausgesetzt, Sie wollten die Variable 'who' haben) erzeugt das Mutex-Entriegeln eine "happen-before" -Beziehung, wie oben beschrieben. Da dies bedeutet, dass andere Threads die Entsperrung des Mutex anzeigen (dh sie können es selbst sperren), sehen sie die aktualisierte Version von x.

+1

"Wie von anderen Themen angezeigt" ist hier der Schlüssel. Anweisungen * innerhalb * eines Threads werden garantiert in der Reihenfolge des Programms ausgeführt (die Reihenfolge, in der sie in Ihrem Code erscheinen). Die Sichtbarkeit von Speicherschreibvorgängen, die von anderen Threads * gesehen werden, ist nicht garantiert. Mutex oder "volatile" ist erforderlich, um überhaupt eine Aussage darüber zu treffen, was Thread 2 außerhalb von "vielleicht" sieht. – markspace

+0

Auch "Vorstellung von 'zurück in den Hauptspeicher schreiben'": ja. Java ist für Hardware geschrieben, die nicht notwendigerweise Cache-kohärent ist. Brian Goetz spricht über diesen Aspekt des Java-Designs in * Java Concurrency in Practice * und sagt, dass Java für solche CPUs entwickelt wurde. Es ist ein bisschen komisch, aber leicht zu gewöhnen. "Volatile" erzeugt jedoch ausdrücklich eine "Flush to main memory", das ist was passiert - vorher bedeutet (neben anderen Dingen). – markspace

+0

Funktionell bin ich mir nicht sicher, dass 'flush to main memory' der richtige Weg ist, um zu formulieren, was passiert. Es ist mein Verständnis, dass volatile in der Regel als eine Speicherbarriere implementiert wird. Diese Konstrukte verursachen nicht unbedingt CPU-Cache-Flushes; Sie beschränken lediglich die Art und Weise, wie Speicher-Neuordnungen auftreten können, die Einfluss darauf haben, wie Prozessoren Werte sehen. –

Verwandte Themen