it-swarm.com.de

Unterschied zwischen flüchtig und in Java synchronisiert

Ich wundere mich über den Unterschied zwischen der Deklaration einer Variablen als volatile und dem Zugriff auf die Variable immer in einem synchronized(this)-Block in Java.

Gemäß diesem Artikel http://www.javamex.com/tutorials/synchronization_volatile.shtml ist viel zu sagen und es gibt viele Unterschiede, aber auch einige Ähnlichkeiten.

Ich interessiere mich besonders für diese Info:

...

  • der Zugriff auf eine flüchtige Variable hat niemals das Potenzial zu blockieren: Wir machen immer nur ein einfaches Lesen oder Schreiben. Daher werden wir im Gegensatz zu einem synchronisierten Block niemals an einer Sperre festhalten.
  • da der Zugriff auf eine flüchtige Variable niemals eine Sperre enthält, ist sie nicht für Fälle geeignet, in denen read-update-write als atomare Operation verwendet werden soll (es sei denn, wir sind bereit, "ein Update zu verpassen").

Was meinen sie mit read-update-write? Ist ein Schreiben nicht auch ein Update oder bedeutet das einfach, dass update ein Schreiben ist, das vom Lesen abhängt?

Vor allem, wann ist es besser geeignet, Variablen volatile zu deklarieren, als auf sie über einen synchronized-Block zuzugreifen? Ist es eine gute Idee, volatile für von der Eingabe abhängige Variablen zu verwenden? Zum Beispiel gibt es eine Variable namens render, die durch die Rendering-Schleife gelesen und durch ein Tastendruckereignis festgelegt wird.

194

Es ist wichtig zu verstehen, dass die Thread-Sicherheit zwei Aspekte hat.

  1. ausführungskontrolle und
  2. speicher Sichtbarkeit

Das erste hat mit der Kontrolle zu tun, wann der Code ausgeführt wird (einschließlich der Reihenfolge, in der die Anweisungen ausgeführt werden) und ob er gleichzeitig ausgeführt werden kann, und das zweite mit der Kontrolle, wann die Auswirkungen im Speicher auf andere Threads sichtbar sind. Da jede CPU über mehrere Cache-Ebenen zwischen ihr und dem Hauptspeicher verfügt, können Threads, die auf verschiedenen CPUs oder Kernen ausgeführt werden, zu einem bestimmten Zeitpunkt "Speicher" unterschiedlich sehen, da Threads private Kopien des Hauptspeichers abrufen und bearbeiten dürfen.

Die Verwendung von synchronized verhindert, dass ein anderer Thread den Monitor (oder die Sperre) für dasselbe Objekt , wodurch verhindert wird, dass alle durch Synchronisation auf demselben Objekt geschützten Codeblöcke gleichzeitig ausgeführt werden. Durch die Synchronisierung und wird eine Speicherbarriere "happen before" erstellt, die eine Einschränkung der Speichersichtbarkeit verursacht, sodass alles, was bis zu dem Zeitpunkt getan wird, an dem ein Thread eine Sperre aufhebt , angezeigt wird für einen anderen Thread, der anschließend dieselbe Sperre erlangt hat, bevor er die sperren. In der Praxis führt dies bei aktueller Hardware in der Regel zu einem Leeren der CPU-Caches, wenn ein Monitor erfasst wird, und zu einem Schreibvorgang in den Hauptspeicher, wenn dieser freigegeben wird. Beide sind (relativ) teuer.

Wenn Sie dagegen volatile verwenden, werden alle Zugriffe (Lesen oder Schreiben) auf die flüchtige Variable auf den Hauptspeicher erzwungen, sodass die flüchtige Variable nicht in den CPU-Caches gespeichert wird. Dies kann für einige Aktionen nützlich sein, bei denen es lediglich erforderlich ist, dass die Variablen korrekt angezeigt werden und die Reihenfolge der Zugriffe nicht wichtig ist. Die Verwendung von volatile ändert auch die Behandlung von long und double, damit der Zugriff auf sie atomar sein muss. Bei einigen (älteren) Geräten sind möglicherweise Sperren erforderlich, bei modernen 64-Bit-Geräten jedoch nicht. Unter dem neuen (JSR-133) Speichermodell für Java 5+ wurde die Semantik der flüchtigen Elemente in Bezug auf Speichersichtbarkeit und Anweisungsreihenfolge fast so stark wie synchronisiert verbessert (siehe http : //www.cs.umd.edu/users/pugh/Java/memoryModel/jsr-133-faq.html#volatile ). Für die Zwecke der Sichtbarkeit wirkt jeder Zugriff auf ein flüchtiges Feld wie eine halbe Synchronisation.

Unter dem neuen Speichermodell ist es immer noch wahr, dass flüchtige Variablen nicht miteinander neu geordnet werden können. Der Unterschied besteht darin, dass es jetzt nicht mehr so ​​einfach ist, normale Feldzugriffe um sie herum neu zu ordnen. Das Schreiben in ein flüchtiges Feld hat den gleichen Speichereffekt wie eine Monitorfreigabe, und das Lesen aus einem flüchtigen Feld hat den gleichen Speichereffekt wie eine Monitorerfassung. Weil das neue Speichermodell die Neuordnung flüchtiger Feldzugriffe mit anderen flüchtigen oder nicht flüchtigen Feldzugriffen strenger einschränkt, ist alles sichtbar, was für den Thread A sichtbar war, wenn er in das flüchtige Feld f schreibt B einfädeln, wenn f gelesen wird.

- Häufig gestellte Fragen zu JSR 133 (Java Memory Model)

Jetzt verursachen beide Formen der Speicherbarriere (unter dem aktuellen JMM) eine Befehlsumordnungsbarriere, die verhindert, dass der Compiler oder die Laufzeit Befehle über die Barriere hinweg umordnet. In der alten JMM verhinderte die Flüchtigkeit nicht die Nachbestellung. Dies kann wichtig sein, da abgesehen von den Speicherbarrieren die einzige Einschränkung darin besteht, dass für einen bestimmten Thread der Nettoeffekt des Codes derselbe ist, der für die Anweisungen gelten würde wurden in genau der Reihenfolge ausgeführt, in der sie in der Quelle erscheinen.

Eine Verwendung von Volatile besteht darin, dass ein gemeinsam genutztes, aber unveränderliches Objekt im laufenden Betrieb neu erstellt wird, wobei viele andere Threads zu einem bestimmten Zeitpunkt in ihrem Ausführungszyklus einen Verweis auf das Objekt erstellen. Die anderen Threads müssen mit der Verwendung des neu erstellten Objekts beginnen, sobald es veröffentlicht wurde. Sie benötigen jedoch nicht den zusätzlichen Aufwand für die vollständige Synchronisierung und die damit verbundenen Konflikte und das Leeren des Caches.

// Declaration
public class SharedLocation {
    static public SomeObject someObject=new SomeObject(); // default object
    }

// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
//       someObject will be internally consistent for xxx(), a subsequent 
//       call to yyy() might be inconsistent with xxx() if the object was 
//       replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
private String getError() {
    SomeObject myCopy=SharedLocation.someObject; // gets current copy
    ...
    int cod=myCopy.getErrorCode();
    String txt=myCopy.getErrorText();
    return (cod+" - "+txt);
    }
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

Sprechen Sie speziell mit Ihrer Frage zum Lesen, Aktualisieren und Schreiben. Betrachten Sie den folgenden unsicheren Code:

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

Wenn die updateCounter () -Methode nicht synchronisiert ist, können zwei Threads gleichzeitig darauf zugreifen. Unter den vielen möglichen Permutationen ist eine, dass Thread-1 den Test für counter == 1000 durchführt, ihn für wahr hält und dann ausgesetzt wird. Dann macht Thread-2 den gleichen Test und sieht es auch wahr und ist suspendiert. Anschließend wird Thread-1 fortgesetzt und der Zähler auf 0 gesetzt. Anschließend wird Thread-2 fortgesetzt und der Zähler erneut auf 0 gesetzt, da die Aktualisierung von Thread-1 fehlgeschlagen ist. Dies kann auch passieren, wenn der Threadwechsel nicht wie beschrieben erfolgt, sondern einfach, weil zwei verschiedene zwischengespeicherte Kopien des Zählers in zwei verschiedenen CPU-Kernen vorhanden waren und die Threads jeweils auf einem separaten Kern liefen. In diesem Fall könnte ein Thread einen Zähler bei einem Wert haben und der andere könnte einen Zähler bei einem ganz anderen Wert haben, nur wegen des Zwischenspeicherns.

In diesem Beispiel ist wichtig, dass der variable Zähler aus dem Hauptspeicher in den Cache gelesen, im Cache aktualisiert und erst zu einem unbestimmten Zeitpunkt später, als eine Speichersperre auftrat, in den Hauptspeicher zurückgeschrieben wurde oder wenn der Cache-Speicher für etwas anderes benötigt wurde. Das Erstellen des Zählers volatile ist für die Thread-Sicherheit dieses Codes nicht ausreichend, da der Test für das Maximum und die Zuweisungen diskrete Operationen sind, einschließlich des Inkrements, das ein Satz von nicht-atomaren read+increment+write Maschinenanweisungen ist. so etwas wie:

MOV EAX,counter
INC EAX
MOV counter,EAX

Flüchtige Variablen sind nur dann nützlich, wenn alle Operationen, die mit ihnen ausgeführt werden, "atomar" sind, wie in meinem Beispiel Wenn ein Verweis auf ein vollständig geformtes Objekt nur gelesen oder geschrieben wird (und normalerweise nur von einem einzelnen Punkt aus). Ein weiteres Beispiel wäre eine flüchtige Array-Referenz, die eine Liste mit Kopien beim Schreiben unterstützt, vorausgesetzt, das Array wurde nur gelesen, indem zuerst eine lokale Kopie der Referenz darauf erstellt wurde.

357
Lawrence Dol

flüchtig ist ein Feldmodifizierer, während synchronisiertCodeblöcke und Methoden ändert. Wir können also drei Varianten eines einfachen Accessors mithilfe dieser beiden Schlüsselwörter angeben:

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1() greift auf den aktuell in i1 im aktuellen Thread gespeicherten Wert zu Threads können lokale Kopien von Variablen haben, und die Daten müssen nicht mit den Daten in anderen Threads übereinstimmen. Insbesondere kann ein anderer Thread i1 in seinem Thread aktualisiert haben, der Wert im aktuellen Thread kann jedoch anders sein dieser aktualisierte Wert. Tatsächlich hat Java die Idee eines "Hauptspeichers", und dieser Speicher enthält den aktuellen "korrekten" Wert für Variablen. Threads können eine eigene Datenkopie für Variablen haben, und die Thread-Kopie kann sich vom Hauptspeicher unterscheiden. In der Tat ist es möglich, dass der "Hauptspeicher" einen Wert von 1 für i1 hat, für Thread1 einen Wert von 2 für i1 und für Thread2), um einen Wert von 3 für i1 zu erhalten, wenn thread1 und thread2 beide i1 aktualisiert haben, aber der aktualisierte Wert wurde noch nicht an "main" weitergegeben. Speicher oder andere Threads.

Andererseits greift geti2() effektiv auf den Wert von i2 aus dem Hauptspeicher zu. Eine flüchtige Variable darf keine lokale Kopie einer Variablen haben, die sich von dem aktuell im "Hauptspeicher" gespeicherten Wert unterscheidet. Eine für volatil deklarierte Variable muss ihre Daten effektiv über alle Threads hinweg synchronisieren lassen. Wenn Sie also auf die Variable in einem beliebigen Thread zugreifen oder sie aktualisieren, wird für alle anderen Threads derselbe Wert angezeigt. Im Allgemeinen haben flüchtige Variablen einen höheren Zugriffs- und Aktualisierungsaufwand als "einfache" Variablen. Im Allgemeinen dürfen Threads eine eigene Datenkopie haben, um die Effizienz zu erhöhen.

Es gibt zwei Unterschiede zwischen flüchtig und synchronisiert.

Zunächst erhält und synchronisiert Sperren auf Monitoren, die jeweils nur einen Thread erzwingen können, um einen Codeblock auszuführen. Das ist der ziemlich bekannte Aspekt der Synchronisierung. Synchronisiert synchronisiert aber auch den Speicher. Tatsächlich synchronisiert synchronisiert der gesamte Thread-Speicher mit dem "Hauptspeicher". So führt geti3() folgendes aus:

  1. Der Thread erwirbt die Sperre auf dem Monitor für das Objekt.
  2. Der Threadspeicher leert alle seine Variablen, d. H. Er lässt alle seine Variablen effektiv aus dem "Hauptspeicher" lesen.
  3. Der Codeblock wird ausgeführt (in diesem Fall wird der Rückgabewert auf den aktuellen Wert von i3 gesetzt, der gerade aus dem "Hauptspeicher" zurückgesetzt wurde).
  4. (Änderungen an Variablen würden normalerweise normalerweise in den "Hauptspeicher" geschrieben, aber für geti3 () haben wir keine Änderungen.)
  5. Der Thread gibt die Sperre auf dem Monitor für dieses Objekt frei. 

Wenn also flüchtig nur der Wert einer Variablen zwischen Thread-Speicher und "Hauptspeicher" synchronisiert wird, synchronisiert Synchronized den Wert aller Variablen zwischen Thread-Arbeitsspeicher und "Hauptspeicher" und sperrt und gibt einen Monitor zum Booten frei. Klar synchronisiert hat wahrscheinlich mehr Overhead als volatil. 

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

93
Kerem Baydoğan

synchronized ist der Zugriffsbeschränkungsmodifizierer auf Methodenebene/Blockebene. Dadurch wird sichergestellt, dass ein Thread die Sperre für den kritischen Abschnitt besitzt. Nur der Thread, dem eine Sperre gehört, kann einen synchronized-Block eingeben. Wenn andere Threads versuchen, auf diesen kritischen Abschnitt zuzugreifen, müssen sie warten, bis der aktuelle Besitzer die Sperre aufhebt. 

volatile ist ein variabler Zugriffsmodifizierer, der alle Threads dazu zwingt, den neuesten Wert der Variablen aus dem Hauptspeicher abzurufen. Für den Zugriff auf volatile-Variablen ist keine Sperrung erforderlich. Alle Threads können gleichzeitig auf den Wert der flüchtigen Variablen zugreifen. 

Ein gutes Beispiel für die Verwendung einer flüchtigen Variablen: Date variable.

Angenommen, Sie haben die Datumsvariable volatile erstellt. Alle Threads, die auf diese Variable zugreifen, erhalten immer die neuesten Daten aus dem Hauptspeicher, sodass alle Threads den tatsächlichen (tatsächlichen) Date-Wert anzeigen. Sie benötigen keine unterschiedlichen Threads, die unterschiedliche Zeiten für dieselbe Variable zeigen. Alle Threads sollten den richtigen Datumswert aufweisen.

 enter image description here

Werfen Sie einen Blick auf diesen Artikel , um das volatile-Konzept besser zu verstehen. 

Lawrence Dol Cleary hat Ihren read-write-update query erklärt. 

Bezüglich Ihrer anderen Fragen

Wann ist es besser geeignet, Variablen als flüchtig zu deklarieren, als über synchronisierte Variablen darauf zuzugreifen? 

Sie müssen volatile verwenden, wenn Sie der Meinung sind, dass alle Threads den tatsächlichen Wert der Variablen in Echtzeit erhalten sollten, wie in dem Beispiel, das ich für die Datumsvariable erläutert habe. 

Ist es eine gute Idee, volatile für Variablen zu verwenden, die von der Eingabe abhängen?

Die Antwort ist dieselbe wie bei der ersten Abfrage. 

Lesen Sie in diesem Artikel nach zum besseren Verständnis.

17
Ravindra babu

Ich mag die jenkovs Erklärung

Sichtbarkeit gemeinsamer Objekte

Wenn zwei oder mehr Threads ein Objekt gemeinsam nutzen, ohne dass die Deklarationen volatile oder synchronisation ordnungsgemäß verwendet werden, sind von einem Thread vorgenommene Aktualisierungen des gemeinsam genutzten Objekts möglicherweise nicht für andere Threads sichtbar.

Stellen Sie sich vor, das Shared Object wird zunächst im Hauptspeicher abgelegt. Ein Thread, der auf einer CPU läuft, liest dann das gemeinsam genutzte Objekt in seinen CPU-Cache. Dort ändert es das Shared Object. Solange der CPU-Cache nicht in den Hauptspeicher zurückgespült wurde, ist die geänderte Version des gemeinsam genutzten Objekts für Threads nicht sichtbar, die auf anderen CPUs ausgeführt werden. Auf diese Weise erhält jeder Thread möglicherweise eine eigene Kopie des gemeinsam genutzten Objekts, wobei sich jede Kopie in einem anderen CPU-Cache befindet.

Das folgende Diagramm veranschaulicht die skizzierte Situation. Ein Thread, der auf der linken CPU ausgeführt wird, kopiert das gemeinsam genutzte Objekt in seinen CPU-Cache und ändert seine Variable count in 2. Diese Änderung ist für andere Threads, die auf der rechten CPU ausgeführt werden, nicht sichtbar, da die Aktualisierung nicht gezählt wurde noch in den Hauptspeicher gespült.

enter image description here

Um dieses Problem zu lösen, können Sie das volatile Schlüsselwort von Java verwenden. Das volatile-Schlüsselwort kann sicherstellen, dass eine bestimmte Variable direkt aus dem Hauptspeicher gelesen wird und bei Aktualisierung immer in den Hauptspeicher zurückgeschrieben wird.

Rennbedingungen

Wenn zwei oder mehr Threads ein Objekt gemeinsam nutzen und mehr als ein Thread Variablen in diesem gemeinsam genutzten Objekt aktualisiert, können Race-Bedingungen auftreten.

Stellen Sie sich vor, Thread A liest die variable Anzahl eines gemeinsam genutzten Objekts in seinen CPU-Cache. Stellen Sie sich auch vor, dass Thread B dasselbe tut, jedoch in einen anderen CPU-Cache. Jetzt fügt Thread A eine Zahl hinzu und Thread B macht dasselbe. Jetzt wurde var1 zweimal in jedem CPU-Cache inkrementiert.

Wenn diese Inkremente nacheinander ausgeführt worden wären, würde der variable Zählerstand zweimal erhöht und der ursprüngliche Wert + 2 in den Hauptspeicher zurückgeschrieben.

Die beiden Inkremente wurden jedoch gleichzeitig ohne korrekte Synchronisation ausgeführt. Unabhängig davon, welche der Threads A und B die aktualisierte Version der Zählung zurück in den Hauptspeicher schreibt, ist der aktualisierte Wert trotz der beiden Inkremente nur um 1 höher als der ursprüngliche Wert.

Dieses Diagramm veranschaulicht das Auftreten des Problems mit den oben beschriebenen Race-Bedingungen:

enter image description here

Um dieses Problem zu lösen, können Sie einen Java-synchronisierten Block verwenden. Ein synchronisierter Block garantiert, dass nur ein Thread zu einem bestimmten Zeitpunkt einen bestimmten kritischen Abschnitt des Codes eingeben kann. Synchronisierte Blöcke garantieren auch, dass alle Variablen, auf die im synchronisierten Block zugegriffen wird, aus dem Hauptspeicher eingelesen werden. Wenn der Thread den synchronisierten Block verlässt, werden alle aktualisierten Variablen wieder in den Hauptspeicher zurückgespeichert, unabhängig davon, ob die Variable für flüchtig erklärt wird oder nicht.

2
yoAlex5

tl; dr :

Beim Multithreading gibt es drei Hauptprobleme:

1) Rennbedingungen

2) Zwischenspeicher/veralteter Speicher

3) Complier- und CPU-Optimierungen

volatile kann 2 und 3 lösen, kann aber nicht 1 lösen. synchronized/explizite Sperren können 1, 2 und 3 lösen.

Ausarbeitung :

1) Betrachten Sie diesen Thread als unsicherer Code:

x++; 

Während es wie eine Operation aussehen mag, ist es eigentlich 3: Den aktuellen Wert von x aus dem Speicher lesen, 1 hinzufügen und in den Speicher zurückspeichern. Wenn wenige Threads versuchen, dies gleichzeitig zu tun, ist das Ergebnis der Operation undefiniert. Wenn x ursprünglich 1 war, kann der Code nach 2 Threads, die den Code bearbeiten, 2 und 3 sein, abhängig davon, welcher Thread den Teil der Operation abgeschlossen hat, bevor die Steuerung an den anderen Thread übergeben wurde. Dies ist eine Form von Race Condition

Die Verwendung von synchronized für einen Codeblock bewirkt, dass es atomic - bedeutet, dass die drei Operationen gleichzeitig ausgeführt werden, und dass kein anderer Thread in die Mitte kommen und interferieren kann. Wenn also x 1 wäre und 2 Threads versuchen, x++ vorzuverformen, werden wir know am Ende gleich 3 sein. Damit ist das Race-Condition-Problem gelöst. 

synchronized (this) {
   x++; // no problem now
}

Wenn Sie x als volatile markieren, wird x++; nicht atomar, so dass dieses Problem nicht gelöst wird.

2) Außerdem haben Threads ihren eigenen Kontext - d. H. Sie können Werte aus dem Hauptspeicher zwischenspeichern. Das bedeutet, dass einige Threads Kopien einer Variablen haben können, aber sie arbeiten mit ihrer Arbeitskopie, ohne den neuen Status der Variablen unter anderen Threads zu teilen. 

Beachten Sie, dass in einem Thread x = 10;. Und etwas später in einem anderen Thread x = 20;. Die Wertänderung von x wird möglicherweise nicht im ersten Thread angezeigt, da der andere Thread den neuen Wert im Arbeitsspeicher gespeichert hat, ihn jedoch nicht in den Hauptspeicher kopiert hat. Oder dass es in den Hauptspeicher kopiert wurde, aber der erste Thread hat seine Arbeitskopie nicht aktualisiert. Wenn nun der erste Thread if (x == 20) prüft, lautet die Antwort false

Durch das Markieren einer Variablen als volatile werden grundsätzlich alle Threads angewiesen, Lese- und Schreibvorgänge nur im Hauptspeicher auszuführen. synchronized weist jeden Thread an, seinen Wert aus dem Hauptspeicher zu aktualisieren, wenn er in den Block eintritt, und das Ergebnis in den Hauptspeicher zu leeren, wenn er den Block verlässt. 

Beachten Sie, dass im Gegensatz zu Datenrennen veralteter Speicher nicht so leicht (erneut) erzeugt werden kann, da ohnehin Flushs in den Hauptspeicher auftreten. 

3) Der Complier und die CPU können (ohne jegliche Synchronisation zwischen Threads) den gesamten Code als Single-Thread behandeln. Das bedeutet, es kann einen Code betrachten, der in einem Multithreading-Aspekt sehr aussagekräftig ist, und ihn so behandeln, als wäre er ein einzelner Thread, bei dem er nicht so aussagekräftig ist. Es kann also einen Code betrachten und sich zur Optimierung entscheiden, den Code neu zu ordnen oder sogar vollständig zu entfernen, wenn er nicht weiß, dass dieser Code für mehrere Threads ausgelegt ist. 

Betrachten Sie den folgenden Code:

boolean b = false;
int x = 10;

void threadA() {
    x = 20;
    b = true;
}

void threadB() {
    if (b) {
        System.out.println(x);
    }
}

Man könnte meinen, ThreadB könnte nur 20 drucken (oder gar nichts drucken, wenn threadB if-check ausgeführt wird, bevor b auf true gesetzt wird), da b erst dann auf true gesetzt wird, nachdem x auf 20 gesetzt ist, der Compiler/die CPU aber möglicherweise entscheide, ThreadA neu zu ordnen, in diesem Fall könnte ThreadB auch 10 drucken. Durch die Markierung von b als volatile wird sichergestellt, dass es nicht neu angeordnet wird (oder in bestimmten Fällen verworfen wird). Welcher gemeine ThreadB konnte nur 20 (oder gar nichts) drucken. Durch das Markieren der Methoden als synchronisiert wird dasselbe Ergebnis erzielt. Durch das Markieren einer Variablen als volatile wird nur sichergestellt, dass sie nicht neu angeordnet wird. Alles vor/nach dem Wert kann jedoch neu angeordnet werden, sodass die Synchronisierung in einigen Szenarien besser geeignet ist.

Beachten Sie, dass vor dem Java 5 New Memory Model dieses Problem durch Volatile nicht gelöst wurde. 

1
David Refaeli