Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
CONTACT 
Details zu volatile-Variablen

Details zu volatile-Variablen
Java Memory Model
Details zu volatile-Variablen

Java Magazin, Oktober 2008
Klaus Kreft & Angelika Langer

Dies ist das Manuskript eines Artikels, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im Java Magazin erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).
 
Die ganze Serie zum  "Java Memory Modell" als PDF (985 KB).
 

Wir haben im letzten Beitrag diskutiert, dass Synchronisation mit Hilfe von Locks Performance kosten kann.  Um dies zu vermeiden, kann die Synchronisation unter Umständen durch andere Techniken ersetzt werden.  Eine Alternative zur Synchronisation ist die Verwendung von volatile-Variablen.  Deshalb wollen wir uns in diesem Beitrag näher mit volatile-Variablen befassen.
 

volatile als Alternative zur Synchronisation

Wir haben schon in einem früheren Beitrag (siehe / JMM1 /) ein Beispiel gezeigt, in dem bewußt auf Synchronisation verzichtet wurde:
public class Processor {
    private volatile boolean connectionPrepared = false;
    public void prepareConnection() {
        // ... open connection ...
        connectionPrepared = true;
    }
    public void start() throws InterruptedException {
        // ... various initializations ...
        while (!connectionPrepared )
            Thread.sleep(500);
        // ... start actual processing ...
    }
}
Hier greifen zwei Methoden einer Klasse konkurrierend auf das boolean-Feld connectionPrepared zu. Die Idee ist, dass die start-Methode in einem Thread ausgeführt wird, der alle vorbereitenden Arbeiten anstößt und dann abwartet, bis alle Vorbereitungen abgeschlossen sind, ehe er die eigentliche Verarbeitung beginnt.  Die beiden Threads kommunizieren miteinander über gemeinsam verwendete veränderliche Daten, nämlich das boolean-Feld connectionPrepared.  Der eine Thread setzt connectionPrepared auf true, wenn er fertig ist, und der andere Thread beobachtet, ob connectionPrepared auf true gesetzt wurde, um dann mit der eigentlichen Arbeit zu beginnen.

Das Lesen und Verändern des boolean Feldes ist nicht unterbrechbar.  Das garantiert die Sprachspezifikation mit der Regel, dass der Zugriff auf Variablen von primitiven Typ (außer long und double) und auf Referenzvariablen atomar ist.  Deshalb braucht man in dem Beispiel keine Synchronisation, um den Zugriff ununterbrechbar zu machen.

Wir haben an diesem Beispiel diskutiert, dass das boolean-Feld connectionPrepared aber als volatile deklariert werden muss, wenn auf Synchronisation verzichtet wird, damit die Modifikation, die der Initialisierungsthread macht, dem wartenden Thread sichtbar gemacht wird.  Diese Deklaration ist notwendig, weil das Java-Memory-Modell (JMM) nur unter bestimmten Bedingungen garantiert, dass Speichermodifikation anderen Threads sichtbar gemacht werden. Welche Bedingungen das sind, haben wir in einem Beitrag über das JMM (siehe / JMM2 /) diskutiert.  Unter anderem gibt es Sichtbarkeitgarantien für Synchronisation, aber auch für volatile-Variablen. Hier noch mal eine kurze Wiederholung:

Das Memory-Modell in Java ähnelt einer abstrakten SMP (= symmetric multi processing)-Maschine: die Threads laufen parallel und konzeptionell haben alle Threads Zugriff auf einen gemeinsamen Hauptspeicher (main memory), in dem die gemeinsam verwendeten Variablen abgelegt sind. Daneben hat jeder Thread einen eigenen lokalen Speicherbereich (cache), in den er Variablen hineinladen und lokal bearbeiten kann.  Das Zurückschreiben der lokalen Daten in den Hauptspeicher (flush) und das Hereinladen von Daten aus dem Hauptspeicher (refresh) muss nach den Regeln des JMM geschehen.

  • Synchronisation. Der Erhalt eines Locks löst einen Refresh aus, das Freigeben des Locks löst einen Flush aus.  Alle Threads, die dasselbe Lock verwenden, durchlaufen die Sequenz der mit diesem Lock synchronisierten Anweisungen nicht parallel sondern nacheinander. Das heißt, es ist klar, dass zunächst ein Thread auf die gemeinsam verwendeten Daten zugreift und erst danach ein anderer.  Da beim Freigeben des Locks der eine Thread alle lokalen Daten in den Hauptspeicher zurück schreiben muss und der nächste Thread beim Erhalt des Locks seinen lokalen Arbeitsspeicher aus dem Hauptspeicher auffrischen muss, sieht der zweite Thread alle Modifikationen, die der erste Thread an den gemeinsam verwendeten Daten vorgenommen hat.
  • Lese- und Schreibzugriff auf volatile-Variablen. Das Lesen einer volatile-Variablen löst einen Refresh aus, das Modifizieren einer volatile-Variablen löst einen Flush aus.  Das bedeutet, dass ein Thread, der den Inhalt einer volatile-Variablen liest, den Wert nicht aus seinem Arbeitsspeicher holen darf, sondern ihn aus dem Main Memory holen muss.  Dabei muss er nicht nur den Inhalt der betreffenden volatile-Variablen holen, sondern er muss seinen gesamten Arbeitsspeicher auffrischen.  Auf diese Weise sieht er alle Modifikation an gemeinsam verwendeten Variablen, die zuvor ein anderer Thread gemacht hat, der auf dieselbe volatile-Variablen vorher schreibend zugegriffen hat. Denn beim schreibenden Zugriff muss der andere Thread seinen gesamten Arbeitspeicher in den Hauptspeicher zurückgeschrieben haben.
Da sowohl Synchronisation als auch volatile Speichereffekte haben, konnten wir in unserem Beispiel auf Synchronisation verzichten und sie durch die Verwendung von volatile ersetzen.  In welchen Situationen funktioniert diese Technik?   Immer dann, wenn Synchronisation nur wegen der Sichtbarkeit verwendet werden würde und aus sonst keinem anderen Grunde (z.B. für die Ununterbrechbarkeit des Zugriffs) gebraucht wird.  Voraussetzung dafür ist, dass der Zugriff auf die betreffenden gemeinsam genutzten Daten bereits an sich atomar ist, d.h. die Technik funktioniert nur für Daten von primitivem Typ oder für Referenzvariablen.
 

Mißverständnis

Bei solchen Optimierungen können sich Fehler einschleichen.  Schauen wir uns einen solchen Fehler an:
public class IntStack {  // falsch; nicht nachmachen !!!
  // attributes:
  private int[] array;
  private int cnt = 0;

  // constructor:
  public int_stack(int capacity) { array = new int[capacity]; }

  // methods:
  public synchronized void push(int elm)  { array[cnt++] = elm; }
  public synchronized int pop()           { return(array[--cnt]); }
  public              int size()          { return cnt; }
}

Es ist das Beispiel eines vereinfachten Stacks von Integerzahlen.  Die Methoden push und pop sind synchronisiert, die Methode size nicht.  Die Methode size kommt ohne Synchronisation aus, weil sie den Wert von cnt zurück liefert.  Das Feld cnt ist vom primitiven Typ int und der lesende Zugriff ist deshalb atomar.  Für die Ununterbrechbarkeit der size-Methode wird die Synchronisation also nicht gebraucht; deshalb wurde sie weggelasen.

Wie ist das aber nun mit der Sichtbarkeit der Modifikationen am cnt-Feld?  Das cnt-Feld ist nicht volatile, aber es sind alle modifzierenden Zugriffe auf das Feld synchronisiert.  Da Synchronisation Flushes auslöst, werden die Änderungen am cnt-Feld sichtbar, die in den Methoden push und pop gemacht wurden.  Also ist die Implementierung in Ordnung - so lautet zumindest ein gelegentlich anzutreffendes Mißverständnis.  Leider ist es nicht ganz so.

Es genügt nicht, dass die Modifikationen am cnt-Feld wegen der Synchronisation der Methoden push und pop gemäß JMM-Regeln in den Hauptspeicher geflusht werden.  Wenn die size-Methode unsynchronisiert auf das cnt-Feld zugreift, dann ist nicht garantiert, dass vor dem Lesezugriff ein Refresh gemacht wurde.  Es kann also passieren, dass ein Thread durch zahlreiche pop-Aufrufe das cnt-Feld inkrementiert und ein anderer Thread, der periodisch mit der size-Methode das cnt-Feld abfragt, diese Änderung nicht zu sehen bekommt, weil der cnt-Wert stets aus dem Cache des Threads und nicht aus dem Hauptspeicher geholt wird.

Es genügt also bereits ein einziger Zugriff ohne Speichergarantien auf eine Variable und die Sichtbarkeit ist nicht mehr gewährleistet.  In dem Beispiel muss daher das cnt-Feld als volatile deklariert werden.

public class IntStack {  // jetzt ist es in Ordnung
  // attributes:
  private int[] array;
  private volatile int cnt = 0;

  // constructor:
  public int_stack(int capacity) { array = new int[capacity]; }

  // methods:
  public synchronized void push(int elm)  { array[cnt++] = elm; }
  public synchronized int pop()           { return(array[--cnt]); }
  public              int size()          { return cnt; }
}

Kosten von volatile

Wir haben volatile als preiswerte Alternative zur Synchronisation vorgestellt, aber natürlich sind auch mit der Verwendung von volatile Kosten verbunden.  Zwar löst der Zugriff auf volatile-Variablen keine Aufwände für die Verwaltung von Warteschlangen oder das Aufwecken von Threads aus Wartezuständen aus.  Es kann auch keinen Stau von Threads geben, die auf den Erhalt eines Locks warten.  Aber der Zugriff auf volatile-Variablen löst Flushes und Refreshes aus, die ungünstig in die Caching-Mechanismen der Prozessoren eingreifen, und ist damit teurer als der Zugriff auf normale Variablen.  Man sollte also auch volatile nicht gänzlich gedankenlos verwenden.

So kann zum Beispiel ein sehr häufiger Zugriff auf eine volatile-Variable, zum Beispiel in einer Schleife, in Summe teurer sein, als die einmalige Synchronisation der gesamten Schleife, weil jeder einzelen Zugriff auf die volatile-Variable einen Refresh oder Flush auslöst, die Synchronisation aber nur jeweils einen Refresh und Flush. Wo jeweils der Trade-off ist, muss man im Einzelfall mit einer Benchmark-Messung bestimmen.  Wir wollen an dieser Stelle nur darauf hinweisen, dass man volatile auch aus Versehen zu ungünstig verwenden kann, dass sich u.U. negative Performance-Effekte einstellen.

Natürlich ist die Benutzung von volatile und Synchronisation mit Hilfe von Locks nicht in allen Belangen gleichwertig. Die Locks sind zusammen mit Conditions in einen Framework eingebettet, der auch komplexere Synchronisation mit Hilfe von wait()/await() und notify()/signal() erlaubt.  Bei volatile ist das nicht so.  Hier bleibt nur das mehrmalige Versuchen (Pollen), bis sich die Situation eingestellt hat, auf die man wartet.  Das muss nicht immer zu schlechten Lösungen führen. Dies kann man an dem ersten Beispiel in diesem Artikel sehen.  Eine Implementierung des Latch-Pattern auf Basis von volatile, bei dem der Startthread wartet, bis die Kommunikationsfunktionalität initialisiert ist, macht durchaus Sinn.  In anderen Situationen können sich aber ernste Nachteile aus dem erhöhten CPU-Verbrauch (auf Grund des Pollens) und der lose gekoppelten Synchronisation beider Threads ergeben. In diesem Sinne muss man auch einige Beispiele in diesem Artikel sehen. Sie haben einen didaktischen, theoretischen Hintergrund: um an möglichst einfachen Beispielen die Speichereffekte von volatile zu diskutieren. Deshalb wrden wir in unserem nächsten Artikel das Thema noch mal von der praktischen Seite am Beispiel des Double-Check-Idioms aufrollen.
 

Speichereffekte von volatile auf andere Variablen

Bislang haben wir nur Beispiele betrachtet, in denen es um den Zugriff auf eine einzige Variable ging.  Wenn der Zugriff darauf atomar war und die Variable keine Abhängigkeiten zu anderen Variablen hatte, dann konnten wir die Synchronisation durch die Verwendung von volatile reduzieren.  Wie ist das bei mehreren Variablen?  Schauen wir uns ein Beispiel an:
class FutureResult {   // korrekt, aber nicht unbedingt empfehlenswert
  private volatile boolean ready = false;
  private Object data = null;

  public Object getResult() {
    if (ready);
    return data;
  }
  public boolean isReady() {
    return ready;
  }
  // only one thread may ever call putResult()
  public void putResult(Object o) {
    data = o;
    ready = true;
  }
}

Hier ist die Idee, dass ein FutureResult von einem Thread verwendet wird, um anderen Threads ein Ergebnis zu übergeben.  Ob das Ergebnis vorliegt und abgeholt werden kann, wird über das boolean-Feld ready angezeigt.  Die empfangenden Threads pollen das Feld mit Hilfe der isReady-Methode und holen das Ergebnis mit getResult ab, sobald isReady true liefert.

Auf Synchronisation wird komplett verzichtet.  Für die Methoden getResult und isReady geht das, weil sie lediglich atomare Operationen ausführen und deshalb nicht unterbrechbar sind.  Die Method putResult braucht keine Synchronisation, weil unterstellt wird, dass nur ein einziger Thread diese Methode einmal aufruft.  Wegen dieser Benutzungskonvention kann es keine konkurrierenden putResult-Aufrufe geben.  Die konkurrierenden Aufrufe von getResult und isReady stellen auch kein Problem dar. Wenn eine dieser Methoden mitten in der putResult-Methode auf die Felder ready und/oder data zugreift, dann liefert isReady schlimmstenfalls noch false zurück, obwohl das Resultat bereits im Feld data abgelegt ist.  Diese vorübergehende Inkonsistenz der Daten ist aber kein Problem, weil der abfragende Thread dann beim nächsten Aufruf von isReady die konsistente Information erhält.

Da auf Synchronisation verzichtet wurde, haben wir unsynchronisierte Zugriffe auf die beiden Felder ready und data und wir müssen für die Sichtbarkeit der Modifikationen an diesen Feldern sorgen.  Das wird getan, indem das boolean-Feld ready als volatile deklariert ist.  Das Referenzfeld data ist hingegen nicht volatile.  Ist das korrekt oder müssen beide Felder volatile sein?

Wenn man die Sichtbarkeitsregeln für volatile-Variablen genau ansieht, stellt man fest, dass es in der Tat genügt, wenn das ready-Feld volatile ist.  Der schreibende Zugriff auf ready in der Methode putResult löst nämlich einen Flush aus.  Dabei wird nicht nur der Inhalt von ready geflusht, sondern es werden alle Speichermodifikationen sichtbar, die der Thread bis dahin gemacht hat, also auch die Modifikation an der Referenzvariablen data.  Die seltsame if-Abfrage in der Methode getResult mit dem leeren Statement im true-Fall dient einem ganz ähnlichen Zweck und ist keineswegs überflüssig.  Der Lesezugriff auf die volatile-Variable ready löst einen Refresh aus, der nicht nur den aktuellen Inhalt von ready aus dem Hauptspeicher beschafft, sondern auch alle anderen Variablen auffrischt, auf die der Thread zugreifen wird.  Damit wird auch der aktuelle Inhalt der Referenzvariablen data dem lesenden Thread sichtbar.

Nun ist das ganze Beispiel etwas seltsam, weil es um maximale Optimierung bemüht ist.  Der Verzicht auf die Synchronisation ist nur wegen der Benutzungskonvention möglich und dann wird auch noch versucht, mit möglichst wenig volatile-Variablen auszukommen.  Im Hinblick auf die Optimierung ist es gut, in Hinblick auf die Verständlichkeit des Codes muss man sich allerdings fragen, ob es hier nicht sinnvoller wäre, beide Felder als volatile zu erklären.  Der Verzicht auf die volatile-Deklaration für die Referenzvarable data führt schließlich zu einem subtilen und damit recht fragilen Code, der bei geringfügigen Änderungen bereits inkorrekt wird.  Der Leser des Source-Code muss folgende Aspekte verstanden haben:

  • Die Reihenfolge der Anweisungen in der Methode putResult ist wichtig, denn der Zugriff auf das volatile-Feld muss ganz am Ende nach allen anderen Modifikationen geschehen, sonst werden die anderen Modifikationen nicht sichtbar.
  • Der doch recht künstlich anmutende Lesezugriff auf das volatile-Feld ready in der Method getResult ist ebenfalls wichtig, weil sonst die Sichtbarkeit des anderen non-volatile Feldes fehlen würde.
Einfacher und vermutlich nicht einmal nennenwert langsamer wäre eine Lösung, in der beide Felder als volatile erklärt sind:
class FutureResult {   // korrekt
  private volatile boolean ready = false;
  private volatile Object data = null;

  public Object getResult() {
    return data;
  }
  public boolean isReady() {
    return ready;
  }
  // only one thread may ever call putResult()
  public void putResult(Object o) {
    data = o;
    ready = true;
  }
}

Wir haben das Beispiel bewußt ausgewählt, um zu demonstrieren, dass Zugriffe auf volatile-Variablen Speichereffekte haben, die nicht nur den Inhalt der volatile-Variablen betreffen, sondern dass alle im Cache eines Threads gehaltenen Variablen durch die Flushes und Refreshes betroffen sind.
 

volatile-Referenzvariablen

Für eine volatile-Referenzvariable gelten dieselben Garantien wie für volatile-Variablen von einem primitiven Typ.  Allerdings beziehen sich alle Regeln stets nur auf die Referenz selbst, also die Adresse des Objekts, nicht aber auf das referenzierte Objekt.  Sehen wir uns das einmal an einem Beispiel genauer an.  Ändern wir die obige Klasse FutureResult so, dass die nicht ein einzelnes Objekt als Resultat enthält, sondern ein Paar von Resultaten:
public class Pair {
     private Object first;
     private Object second;

     public void addFirst(Object o) {
            first = o;
     }
     public void addSecond(Object o) {
            second = o;
     }
     public Object[] toArray() {
            return new Object[] {first,second};
     }
}
public class FutureResult {
    private volatile Pair data = null;

    public Object[] getResult() {
        return (data==null)?null:data.toArray();
    }
    public void isReady() [
        return data != null;
    }
    // only one thread may ever call putResult()
    public void putResult(Object o1,Object o2) {
        Pair tmp = new Pair();
        tmp.addFirst(o1);
        tmp.addSecond(o2);
        data = tmp;
    }
}

Die FutureResult-Klasse hat dieselben Benutzungskonventionen wie zuvor: nur ein Thread darf die putResult-Methode einmal aufrufen und die Empfänger des Resultats dürfen getResult erst aufrufen, wenn isReady true geliefert hat.  In diesem Falle kommen wir wieder ganz ohne Synchronisation aus.  Die Frage ist nun, ob es genügt, dass die Referenzvariable data als volatile erklärt ist.  Schließlich interessieren den Empfanger des Resultats die Inhalte des Pair-Objekts und nicht dessen Adresse.

Die Methode putResult erzeugt und füllt ein temporäres Pair-Objekt und weist danach der volatile-Variablen data die Adresse dieses temporären Objekts zu.  Diese Adressezuweisung löst den Flush aus und dabei werden alle ggf. im Cache des Threads gemachten Speichermodifikationen in den Hauptspeicher geschrieben.  Damit werden garantiert auch die Inhalte des referenzierten Pair-Objekts sichtbar.  Die Methoden getResult und isReady müssen lesend auf die volatile-Variablen data zu, ehe sie den Inhalt des referenzierten Pair-Objekts anschauen können.  Das Lesen der Adresse löst den Refresh aus, der auch die Inhalte des referenzierten Pair-Objekts sichtbar macht.

Wichtig ist hierbei, dass die Methode putResult die Modifikation der Adresse nach der Modifikation der Inhalte des referenzierten Objekts macht.  Folgende Implementierung wäre daher falsch:

    public void putResult(Object o1,Object o2) {
        data = new Pair();
        data.addFirst(o1);
        data.addSecond(o2);
    }
Hier erfolgt die Modifikation auf die volatile-Referenzvariable vor den Modifikationen des Objekts.  Geflusht werden daher die Default-Inhalte des konstruierten Pair-Objekts.  Ob die danach erfolgten Modifikationen am Pair-Objekt in den Hauptspeicher geschrieben werden, ist nicht gesichert.  Es kann also passieren, dass der empfangende Thread das Ergebnis nie zu Gesicht bekommt.

Damit die putResult() Methode von oben korrekt funktioniert, kann die Pair Klasse so geändert werde, dass die beiden Felder first und second auch volatile sind:

public class Pair {
     private volatile Object first;
     private volatile Object second;

     public void addFirst(Object o) {
            first = o;
     }
     public void addSecond(Object o) {
            second = o;
     }
     public Object[] toArray() {
            return new Object[] {first,second};
     }
}

Jetzt löst auch die Zuweisung in data.addFirst(o1) bzw. data.addSecond(o2) jeweils einen eigenen Flush aus und macht damit die Änderungen für andere Threads sichtbar.

Der Ansatz, weitere geschachtelt enthaltene Felder volatile zu machen, hat natürlich seine Limitationen: Wenn wir keinen Zugriff auf den Sourcecode der Klasse (in unserem Fall Pair) haben, ist dies nicht möglich.  Ein anderes Beispiel sind Java Arrays. Hier ist es auch nicht möglich, die Elemente des Arrays explizit volatile zu machen., sodass die korrekte Implementierung unseres Beispiels mit einem Object-Array so aussieht:

public class FutureResult {
    private volatile Object[] data = null;

    public Object[] getResult() {
        return data;
    }
    public void isReady() [
        return data != null;
    }
    // only one thread may ever call putResult()
    public void putResult(Object o1,Object o2) {
        Object[] tmp = new Object[2];
        tmp[0] = o1;
        tmp[1] = o2;
        data = tmp;
    }
}

Zum Abschluss noch ein Hinweis auf ein gelegentlich anzutreffendes Missverständnis: die Sichtbarkeitsregeln, die wir in den diskutierten Beispielen ausgenutzt haben, gelten immer nur dann, wenn die Zugriffe auf die volatile-Variablen zusammen passen. Das heißt, wenn ein Thread durch den schreibenden Zugriff auf eine volatile Variable einen Flush auslöst, dann werden die Speichermmodifikationen nicht allen Threads sichtbar, sondern nur denjenigen, die einen lesenden Zugriff auf diesselbe volatile Variable machen.  Diese Randbedingung wird gelegentlich übersehen, ist aber eigentlich nicht besonders überraschend.  Für die Speichereffekte im Zusammenhang mit dem Anfordern und Freigeben von Locks gilt genau dasgleiche: wenn ein Thread durch durch das Freigeben eines Locks einen Flush auslöst, dann werden die Speichermodifikationen nicht allen Threads sichtbar, sondern nur denjenigen, die dasselbe Lock anschließend anfordern und bekommen.

Zusammenfassung

In diesem Beitrag haben wir die Speichereffekte von volatile Variablen diskutiert. Der Gedanke dahinter ist: man möchte Datensynchronization, wo möglich und erwünscht, durch volatile zu ersetzen. Dadurch lassen sich Java Programme stärker parallelisieren, sodass sie auf Multicore- und Multi-Prozessor-Architekturen besser skalieren. Beim nächsten Mal wollen wir uns das Ganze an einem Beispiel aus der Praxis, dem Double-Check-Idiom, ansehen.
 

Literaturverweise und weitere Informationsquellen

Die gesamte Serie über das Java Memory Model:

/JMM1/ Einführung in das Java Memory Model: Wozu braucht man volatile?
Klaus Kreft & Angelika Langer, Java Magazin, Juli 2008
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/37.JMM-Introduction/37.JMM-Introduction.html
/JMM2/ Überblick über das Java Memory Model
Klaus Kreft & Angelika Langer, Java Magazin, August 2008
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/38.JMM-Overview/38.JMM-Overview.html
/JMM3/ Die Kosten der Synchronisation
Klaus Kreft & Angelika Langer, Java Magazin, September 2008
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/39.JMM-CostOfSynchronization/39.JMM-CostOfSynchronization.html
/JMM4/ Details zu volatile-Variablen
Klaus Kreft & Angelika Langer, Java Magazin, Oktober 2008
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/40.JMM-volatileDetails/40.JMM-volatileDetails.html
/JMM5/ volatile und das Double-Check-Idiom
Klaus Kreft & Angelika Langer, Java Magazin, November 2008
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/41.JMM-DoubleCheck/41.JMM-DoubleCheck.html
/JMM6/ Regeln für die Verwendung von volatile
Klaus Kreft & Angelika Langer, Java Magazin, Dezember 2008
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/42.JMM-volatileIdioms/42.JMM-volatileIdioms.html

 

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
 
Concurrent Java - An in-depth seminar covering all that is worth knowing about concurrent programming in Java, from basics such as synchronization over the Java 5.0 concurrency utilities to the intricacies of the Java Memory Model (JMM).
4 day seminar ( open enrollment and on-site)
 

 
  © Copyright 1995-2015 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/40.JMM-volatileDetails/40.JMM-volatileDetails.html  last update: 22 Mar 2015