Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | NEWSLETTER | 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 
NEWSLETTER 
CONTACT 
Regeln für die Verwendung von volatile

Regeln für die Verwendung von volatile
Java Memory Model
Regeln für die Verwendung von volatile

Java Magazin, Dezember 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 in den vorangegangenen Beiträgen dieser Kolumne erläutert, welche Garantien das Java Memory Modell bezüglich der Verwendung von volatile-Feldern gibt und dass der Einsatz von volatile eine Alternative zur Verwendung von Locks darstellt.  Es stellt sich die Frage: wann ist es sinnvoll, volatile zu verwenden und wann sollte man besser Locks oder andere Synchronisationsmechanismen benutzen?  In diesem Beitrag wollen dieser Frage ein Stück weit nachgehen.
 

Effekte von Synchronisation

Synchronisationsmechanismen haben in der Regel zwei Effekte:
  • Atomicity . Synchronisationsmechanismen garantieren, dass gewisse Operationen oder Sequenzen von Operationen ununterbrechbar (atomar) sind.  Das bedeutet, dass von den Threads, die an der Synchronisation beteiligt sind, immer nur ein Thread zu einer Zeit exklusiv die atomare Sequenz von Operationen ausführt; alle anderen an der Synchronisation beteiligten Thread müssen warten.  Die atomare Sequenz wird von den Threads also stete nacheinander, aber nie gleichzeitig oder ineinander verschränkt ausgeführt.
  • Visibility . Synchronisationsmechanismen bieten Sichtbarkeitsgarantieren, d.h. dass Änderungen, die die an der Synchronisation beteiligten Threads im Speicher gemacht haben, anderen beteiligten Threads sichtbar werden.
Wir haben Synchronisation mit Locks und Synchronisation mit volatile in den letzten Artikel ausführlich diskutiert.

Locks sorgen dafür, dass in einem Thread die Operationen zwischen Anfordern und Freigeben desselben Locks nicht konkurrierend mit Operationen anderer Threads ausgeführt werden, die ebenfalls dasselbe Lock verwenden.  Das bezeichnet man als Mutual Exclusion und es führt dazu, dass Sequenzen von Operationen atomar werden.  Daneben hat das Anfordern und Freigeben von Locks Speichereffekte: das Anfordern eines Lock löst einen Refresh des thread-spezifischen Caches aus, das Freigeben löst einen Flush aus.  Dadurch werden Änderungen, die ein Thread unter dem Schutz eines Locks im Speicher gemacht hat, anderen Threads, die dasselbe Lock verwenden, sichtbar.

volatile hat die gleichen Speichereffekte wie Locks, aber es hat wesentlich schwächere Atomicity-Eigenschaften. Das Lesen einer volatile -Variable löst einen Refresh des thread-spezifischen Caches aus, das Schreiben löst einen Flush aus.  Dadurch werden Änderungen, die ein Thread vor und mit dem Schreibzugriff auf eine volatile -Variable im Speicher gemacht hat, anderen Threads, die lesend auf dieselbe volatile -Variable zugreifen, sichtbar. Der lesende und schreibende Zugriff auf volatile -Variable ist atomar, Sequenzen von solchen Zugriffen sind es aber nicht.  Die Atomicity-Garantie gilt also nur für den einfachen lesenden oder schreibenden Zugriff auf die einzelen volatile -Variable.  Inkrement und Dekrement von numerischen volatile -Variable sind zum Beispiel keine atomaren Operationen.  Bei volatile -Referenzvariablen beziehen sich die Garantieren nur auf die Referenz, nicht auf das referenzierte Objekt (oder Array).

Es ist außerdem zu beachten, dass die Garantien immer nur dann gelten, wenn die Zugriffe zusammenpassen: Sichtbarkeit und Ununterbrechbarkeit gibt es nur für die Threads, die dasselbe Lock verwenden oder auf dieselbe volatile -Variable zugreifen. Anfordern/Freigeben von Lock A verhindert nicht, dass ein Thread konkurrierend läuft, der Lock B anfordert/freigibt.  Analog, der lesende Zugriff auf volatile-Variable A macht nicht sichtbar, was ein anderer Thread bis zum schreibenden Zugriff auf volatile-Variable B im Speicher geändert hat.  Der Mismatch passiert oft versehentlich und führt dann unvermeidlich zu Fehlern.

Regeln für den Einsatz von volatile

Wir haben in vorangegangenen Artikeln die Verwendung von volatile als Alternative zur Synchronisation mithilfe von Locks besprochen.  Da die Garantien für volatile-Variablen geringer sind als für Locks, sind volatile-Variablen natürlich nicht immer eine Alternative.  Es geht nur, wenn die Ununterbrechbarkeit, die die Locks liefern, nicht gebraucht wird.  Das ist in folgenden Fällen der Fall:

Man kann den Zugriff auf Variablen mit volatile statt mit Locks synchronisieren, wenn:
 
Regel 1:
  • der zu schreibende neue Wert der Variablen unabhängig vom gegenwärtigen Wert ist, und
  • Regel 2:
  • die Variable unabhängig von anderen Variablen ist.
  • Unabhängigkeit von gegenwärtigen Wert

    Warum ist es wichtig, dass der neue Wert vom alten unabhängig ist?  Das liegt daran, dass bei einer solchen Abhängigkeit erst einmal der alte Wert gelesen werden muss, dann muss darauf basierend der neue Wert errechnet werden und dann muss der neue Wert geschrieben werden.  Das ist eine Read-Modify-Write-Sequenz, die als Ganzes ununterbrechbar sein muss, sonst kommt bei der Operation was anderes heraus, als beabsichtigt ist.  volatile-Variablen bieten aber keine ununterbrechbaren Sequenzen von Operationen an.  Also geht das mit volatile nicht.

    Hier ist ein Beispiel:

    public class Counter {  // Vorsicht: falsch !!!
      private volatile int value;
      public int getValue()  { return   value; }
      public int increment() { return ++value; }
      public int decrement() { return --value; }
    }
    Dieser angeblich thread-sichere Zähler ist nicht thread-sicher, weil die Inkrementierung und Dekrementierung unterbrechbar sind.  Ein benutzender Thread bekommt unter Umständen beim Inkrementieren vom Zähler mit Wert 2 als neuen Wert 4 heraus, oder irgendeinen anderen von 3 verschiedenen Wert, weil beim Inkrementieren ein anderer Thread dazwischen gekommen ist.

    Das obige Problem kann man elegant mit atomaren Variablen lösen.  Der AtomicInteger zum Beispiel hat ununterbrechbare Inkement- und Dekrement-Operationen.  Das wollen wir an dieser Stelle aber nicht näher betrachten.  Atomare Variablen werden wir uns in einem der nächsten Beiträge ansehen.

    Unabhängigkeit von anderen Variablen

    Warum ist es wichtig, dass die volatile-Variable unabhängig von anderen Variablen ist?  Das liegt daran, dass sie dann Teil einer Invarianten ist und die Invariante nur als Ganzes geändert werden kann.  Die Invariante eines Objekts beschreibt, wann die Felder des Objekts in einem konsistenten und gültigen Zustand sind.  Wenn nur ein Teil der Invarianten geändert wird, dann entsteht in der Regel ein inkonsistenter und ungültiger Zustand des Objekts.  Man kann also nicht nur einen Teil des Objekts ändern, sondern man muss dafür sorgen, dass alle zur Invarianten gehörenden Teile auf einmal geändert werden und keine ungültigen Zwischenzustände sichtbar werden.  Es muss also eine ganze Sequenz von Modifikationen auf den verschiedenen Teilen der Invarianten ununterbrechbar sein.  Das leisten volatile-Variablen leider nicht.

    Hier ist ein Beispiel:

    public class NumberRange {  // Vorsicht: falsch !!!
      private volatile int lower;
      private volatile int upper;
      public void setLower(int i) {
            if (i > upper) throw new IllegalArgumentException(…);
            lower = i;
      }
      public boolean isInRange(int i) {
            return (i >= lower && i <= upper);
      }
      …
    }
    Die Invariante hier ist: lower muss kleiner als upper sein.  Damit diese Invariante erhalten bleibt, muss in der Methode setLower() der Wert von upper gelesen werden, verglichen werden, und dann muss ein neuer Wert für lower geschrieben werden.  Jede dieser Operationen ist einzeln betrachtet atomar, aber die Sequenz ist es nicht.  Das müsste sie aber sein, sonst könnte folgendes passieren: Nehmen wir an, der Bereich ist [2;5]. Der Wert von upper (also 5) wird gelesen und mit dem neuen lower (z.B. 4) verglichen wird. Dann kommt aber ein anderer Thread dazwischen, der upper auf 3 ändert, was der erste Thread nicht merkt, so dass am Ende der ungültige Bereich [4;3] herauskommt.
     

    Beispiele für den erfolgreichen Einsatz von volatile

    Wie man an den Beispielen sehen kann, ist der Einsatz von volatile fehleranfällig und unter Umständigen schwieriger als die Synchronisation mit Locks.  Man kann sich an den oben genannten Regeln orientieren.  Im Folgenden wollen wir ein paar typische Idiome zeigen, in denen der Einsatz von volatile sinvoll und korrekt ist.

    Einmalige Ereignisse

    Variablen, die genau einmal geändert werden und sich danach nie wieder ändern, können gut mit volatile-Variablen ausgedrückt werden.  Bei solchen Abstraktionen, die nach der Änderung in dem neuen Zustand hängen bleiben, spricht man von einem Latch.  Solche Latches werden gerne als Start- oder Ende-Signale verwendet.

    Hier ist ein Beispiel:

    class Service {
      private volatile boolean shutdownRequested;
      ...
      public void shutdown() {
        shutdownRequested = true;
      }
      public void doWork() {
        while (!shutdownRequested) {
            // do stuff
        }
      }
    }
    Hier ist das Latch eine Boolesche Variable, die als Ende-Signal verwendet wird.  Das Status-Flag shutdownRequested kann nur in eine Richtung (von false auf true) gesetzt werden.  Danach ändert es sich nicht mehr.

    Hier ist das Flag nicht Teil einer Invarianten; das Flag ist unabhängig vom restlichen Zustand des Objekts.  Der neue Wert des Flags hängt nicht vom alten Wert des Flags ab.  Synchronisation wird auch aus sonst keinem Grunde gebraucht, weil der Zugriff auf den primitiven Typ boolean sowieso atomar ist.  Man braucht hier nur die Garantie, dass der Wert, den der Shutdown-Thread per Aufruf von shutdown() gesetzt hat, dem Worker-Thread sichtbar wird.  Diese Sichtbarkeit garantiert die volatile-Deklaration. Also kann man hier auf Synchronisation verzichten.

    Wichtig ist bei diesem Idiom, dass es sich bei der Modifikation der volatile-Variablen um ein Latch handelt.  Wenn die Variable toggelt, also zwischen true und false hin- und herpendelt, dann wird man möglicherweise andere Synchronisationsmechanismen (wie Locks oder atomare Variablen) brauchen, weil man zum Beispiel verhindern will, dass Zustandsänderungen verloren gehen.  volatile sorgt nur für die Sichtbarkeit. Wer sich die Variable wann und wie oft anschaut oder ändert oder über Änderungen informiert wird, ist über volatile nicht geregelt.

    Hier noch ein Beispiel für ein solches One-Time-Event:

    public class Precursor {
      public volatile Service theService;

      public void initInBackground() {
        ... do lots of stuff ...
        // this is the only write to theService
        theService = new Service();
      }
    }
    public class Worker {
      public void doWork() {
        while (true) {
          ... do some stuff ...
          // use the service, but only if it is ready
          if (backgroundLoader.theService != null)
            doSomething(backgroundLoader.theService);
        }
      }
    }

    Hier ist das Latch keine Boolesche Variable, sondern eine Referenz, die ihren Wert einmal von null auf einen von null verschiedenen Wert ändert. Diese Änderung wird als Startsignal betrachtet: der Worker-Thread fängt erst an zu arbeiten, wenn der Service verfügbar ist.

    Ohne die volatile-Deklaration könnte es sein, dass die Adresse nicht sichtbar wird, oder nur die Adresse, nicht aber die Inhalte des referenzierten Objekts. Man braucht hier die Garantie, dass die Referenz, die der vorbereitende Thread gesetzt hat, dem Worker-Thread sichtbar wird, inklusive des Inhalts des referenzierten Objekts.  Diese Sichtbarkeit garantiert die volatile-Deklaration der Referenzvariablen theService.  Beim Schreibzugriff wird alles sichtbar gemacht, was bis zu diesem Zeitpunkt im Speicher gemacht wurde, d.h. es wird die Adresse des neuen Service-Objekts sichtbar und dessen Inhalt, weil das Objekt vor der Adresszuweisung erzeugt wurde.

    Die volatile-Deklaration genügt, weil die Referenz auf das Service-Objekt nicht Teil einer Invarianten, sondern unabhängig von allem anderen ist.  Der neue Wert der Referenz hängt auch nicht vom alten Wert der Referenz ab.  Synchronisation wird auch aus sonst keinem Grunde für die Veröffentlich des Service-Objekts gebraucht, weil der Zugriff auf Referenzen sowieso atomar ist.

    Die volatile-Deklaration bewirkt aber nicht, dass die nachfolgenden Zugriffen auf das Service-Objekt sicher sind. Gesichert ist hier nur, dass die erstmalige Veröffentlichung im Rahmen der Initialisierung des Service-Objekts funktioniert, d.h. dass seine Adresse und seine Inhalte den Worker-Threads sichtbar werden. Was danach passiert, ist unklar; jedenfalls hat es nichts mit der volatile-Deklaration zu tun, sondern für die Sicherheit der nachfolgenden Zugriffen auf das Service-Objekt muss mit anderen Mitteln gesorgt werden.

    Mehrfach-Veröffentlichung von Information

    Betrachten wir den Fall, dass Informationen nicht nur einmal zur Verfügung gestellt werden soll, sondern immer wieder neue Information anderen Threads gegenüber veröffentlicht werden soll.  Kann man dafür volatile gebrauchen?  Unter Umständen geht es - und zwar immer dann, wenn sich die veröffentlichte Information selbst nicht verändert, sondern stets durch komplett neue ersetzt wird.

    Hier ist ein Beispiel:

    class TemperatureSensor {
        private volatile double theTemperature;

        public void setTemperature(double temperature) {
            theTemperature = temperature;
        }
        public void displayTemperature() {
            double currentTemparature = theTemperature;
            ... display currentTemparature ...
        }
    }

    Hier legt ein Sensor-Thread Temperaturinformation in einer volatile-Variablen ab, die andere Threads lesen und anzeigen.

    Hier ist der double-Wert nicht Teil einer Invarianten; es gibt keine weiteren Felder.  Der neue Temperaturwert hängt nicht vom alten Wert ab.  Der Zugriff auf den double-Wert wäre ohne die volatile-Deklaration nicht atomar, aber wenn die double-Variable als volatile deklariert ist, dann ist der Zugriff atomar;  Locks werden für Atomarität jedenfalls nicht gebraucht.  Man braucht allerdings die Garantie, dass der Wert, den der Sensor-Thread per Aufruf von setTemperature() gesetzt hat, dem Display-Thread sichtbar wird.  Diese Sichtbarkeit garantiert ebenfalls die volatile-Deklaration. Also kann man hier auf Synchronisation verzichten.

    Allerdings muss man beachten, dass sich der Inhalt des der double-Variablen jederzeit ändern kann.  Es können Änderungen verloren gehen, in dem Sinne, dass der Display-Thread sie nicht anzeigt, weil er sie verpasst hat.  Es kann auch passieren, dass sich gar nichts geändert hat und der Display-Thread denselben Wert mehrmals anzeigt.  Solange das akteptabel ist, funktioniert das Idiom. Sobald aber Werte nicht verloren gehen dürfen oder Mehrfachverwertungen desselben Werts unerwünscht sind, braucht man andere  Synchronisationsmechanismen (wie Locks oder atomare Variablen, vielleicht sogar Conditions).

    Kombination von Lock und volatile

    Interessant ist die Verwendung von volatile in Kombination mit Locks. Ein Beispiel dafür ist das Double-Check-Locking, das wir im letzten Beitrag (siehe / JMM5 /) besprochen haben.  Es gibt aber auch andere Beispiele für die Kombination von volatile und Locks.

    Betrachten wir noch einmal das anfängliche Beispiel der Counter-Klasse.  Eine Lösung mit volatile allein ist nicht möglich, weil Inkrement und Dekrement unterbrechbare Operationen sind.  Wenn man für die Ununterbrechbarkeit von increment() und decrement() mit Hilfe von Locks sorgt, dann bleibt aber immer noch die getValue()-Methode, die bereits atomar ist und eigentlich keine Locks braucht.  Man könnte jetzt alle Methoden als synchronized deklarieren oder man versucht eine preiswertere Lösung, indem man Synchronisation per Lock und per volatile kombiniert.  Das sähe so aus:

    class Counter {
      private volatile int value;
      public              int getValue()  { return   value; }
      public synchronized int increment() { return ++value; }
      public synchronized int decrement() { return --value; }
    }
    value ist nicht Teil einer Invarianten, weil es keine weiteren Felder gibt.  Der neue Wert hängt zwar vom alten Wert des Flags ab; deshalb brauchen wir Synchronisation in increment() und decrement().  Aber die Methode getValue() macht keine Sequenz von Operationen, die von alten Wert abhinge; getValue() ist eine rein lesende Methode und ändert sowieso nichts am Objekt.  Es spricht also nichts dagegen, sich in der Methode getValue() allein auf die Garantien von volatile zu verlassen. Atomarität haben wir schon, weil value vom Typ int ist, und die Sichtbarkeit bekommen wir durch die volatile-Deklaration. Also kann man in der Methode getValue()  auf Synchronisation verzichten.

    Dieses Beispiel ist natürlich etwas komplex, weil es gleich zwei Synchronisationsmechnismen gleichzeitig einsetzt.  In komplizierten Fällen kann die gleichzeitige Verwendung beider Mechanismen leicht zu Fehlern führen.

    Ziel des Einsatzes von volatile

    Wie die Beispiele gezeigt haben, gibt es eine Reihe von Situationen, in denen volatile als Synchronisationsmechanismus einsetzbar ist.  Wenn man sich nicht sicher ist, dann hat man generell immer die Möglichkeit, an allen fraglichen Stellen Locks zu verwenden.  Warum dann also überhaupt volatile einsetzen?

    Wir haben dazu in der vorletzten Ausgabe / JMM3 / besprochen, dass das Anfordern und Freigeben von Locks tendenziell für die Virtuelle Maschine aufwändiger ist als der Zugriff auf volatile-Variablen und deshalb mehr Performance kostet. volatile ist aber auch nicht kostenlos, weil es sogenannte Memory Barriers auslöst, die sich ungünstig auf den Caching-Mechanismus der Hardware auswirken.  Generell ist das Anfordern und Freigeben von Locks weniger performant als der Zugriff auf volatile-Variablen und der ist wiederum weniger performant als der Zugriff auf non-volatile-Variablen, wobei die Peformance-Unterschiede je nach Plattform anders sein können. Das spricht für den Einsatz von volatile als Syncrhonisationsmechanismus zum Zwecke der Performance-Verbesserung.

    Andererseits wenden die Virtuellen Maschinen ein ganze Reihe Optimierungstechniken für Locks an. Bei der Sun JVM ab Version 6.0 sind es zum Beispiel (siehe / PERF / und / JTP /):

    • Lock Coarsening. Dabei wird ein Lock über längere Zeit (z.B. für eine ganze Schleife) gehalten, statt es mehrfach für eine kürzere Zeit (z.B. je Schleifendurchlauf) anzufordern und freizugeben. (siehe Option -XX:-EliminateLocks)
    • Biased Locking. Das ist eine Optimierung für Locks, die nicht oder nur von einem einzigen Thread angefordert werden. (siehe Option -XX:-UseBiasedLocking)
    • Adaptive Locking. Mit dieser Optimierung wird versucht, die Kosten des Auslagerns und Wiederaktivierens eines Thread, der auf ein Lock wartet, zu reduzieren.
    • Lock Elision.  Dabei wird durch eine sogenannte Escape Analysis festgestellt, ob ein Lock-Objekt nur lokal in einer Methode benutzt wird.  Dann wäre das Locking überflüssig und wird entfernt. (siehe Option -XX:-DoEscapeAnalysis)
    Was für die Performance am Ende am günstigsten ist, muss man - wie eigentlich immer, wenn es um Performance-Aussagen geht - per Micro-Benchmark bestimmen.  Es überlagern sich im Einzelfall so viele Effekte, dass man allgemein gesprochen nur Tendenzen nennen kann.

    Selbst wenn der Performance-Gewinn durch die Verwendung von volatile nicht groß ist, dann hat volatile als Synchronisationstechnik immer noch einen Vorteil gegenüber den Locks: es skaliert besser, weil immer ein Thread erfolgreich zugreifen kann.  Es gibt keine Wartezustände und deshalb kann es auch keine Deadlocks geben. Das heißt, Situationen, in denen alle an der Synchronisation beteiligten Threads aufeinander warten, können mit volatile nicht passieren.

    Zusammenfassung

    In diesem Beitrag haben wir uns detailliert angesehen, worauf man achten muss, wenn man volatile-Variablen als Synchronisationsmechanismus anstelle von Locks einsetzen will. Ziel einer solche Maßnahme ist in der Regel die Verbesserung der Performance und/oder der Skalierbarkeit.  volatile ist als Synchronisationsmechanismus aber schwächer als Locks, weil es zwar die gleichen Sichtbarkeitsgarantien gibt, aber kaum für Ununterbrechbarkeit von Zugriffen sorgt; lediglich einfache Lese- und Schreibzugriffe auf Referenzvariablen und Variablen von primitivem Typ sind atomar.  Deshalb kann der Zugriff auf Variablen nur dann mit volatile statt mit Locks synchronisiert werden, wenn es keine Sequenzen von Operationen gibt, die ununterbrechbar sein müssen.  Dazu muss der zu schreibende neue Wert der Variablen unabhängig vom gegenwärtigen Wert sein und die Variable darf nicht Teil einer Invarianten sein, sondern muss unabhängig von anderen Variablen sein.

    In den nächsten Beiträgen sehen wir uns an, welche Garantien das Java Memory Modell für final-Felder gibt und wozu man diese Garantien braucht.
     

    Literaturverweise und weitere Informationsquellen

    / PERF / Java SE 6 Performance White Paper
    Sun Microsystems, Inc.
    URL: http://java.sun.com/performance/reference/whitepapers/6_performance.html#2.1
    / JTP / Synchronization optimizations in Mustang
    Brian Goetz
    IBM developerWorks, Oktober 2005
    URL: http://www.ibm.com/developerworks/java/library/j-jtp10185/index.html

    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/42.JMM-volatileIdioms/42.JMM-volatileIdioms.html  last update: 22 Mar 2015