© Excellent backgrounds/Shutterstock.com
Java Magazin
Teil 2: Problemkategorien im Java Memory Model

Nebenläufigkeit im Zentrum

Die Spezifikation des Java Memory Model gehört zu der kompliziertesten im Java-Umfeld. Ihr Verständnis ist aber in Zeiten von Mehrkernprozessoren extrem wichtig, um Java-Applikationen zu schreiben, die Nebenläufigkeit korrekt unterstützen.

Rodion Alukhanov, Vadym Kazulkin


ArtikelserieTeil 1: Hardware-Memory-Modelle reloadedTeil 2: Problemkategorien im Java Memory ModelTeil 3: Das Open JDK Java Concurrency Stress Tests ToolTeil 4: Die Zukunft des Java Memory Models

Die Reihenfolge von Memory-Operationen wird oft durchmischt. Schuld daran sind Befehls-Reorderings in der CPU, Probleme bei der Synchronisierung von CPU und Cache, der Java-Compiler oder der Java-Just-in-time-Compiler. Die Kontrolle von Memory-Barrieren ist bei der Entwicklung in den meisten Fällen sehr komplex. Java-Spezifikationen vereinfachen das darunterliegende Hardware Memory Model. Das Java Memory Model wird in der Literatur über drei Problemkategorien [1] erklärt. Das sind: Atomicity, Visibility, Ordering.

Seit Java 1.0 ist es so, dass lediglich 32-Bit-Zuweisungen atomic stattfinden. Sollte ein Austausch von Informationen zwischen mehreren Threads komplexer sein, muss man die Blöcke synchronisieren. Lieblingsbeispiel ist dabei ein Inkrement (Abb. 1). Trotz der Kürze des Statements erfolgen im Hintergrund drei Operationen, die ohne Synchronisation zu unerwünschten Ergebnissen in einer Multi-Thread-Anwendung führen können.

Abb. 1: Blöcke synchronisieren

Die klassische Lösung, nicht nur in Java, ist die Benutzung von synchronisiertem Code. Durch das bereits in der ersten Java-Version eingeführte Schlüsselwort synchronized werden die Blöcke markiert, die nur von einem Thread ausgeführt werden dürfen. Auch die Möglichkeit mit dem Schlüsselwort volatile auf eine 64-Bit-Variable ohne Synchronisierung zuzugreifen, gehörte immer zum Java-Sprachumfang.

Bei Atomicity werden noch keine Multi-Core-spezifischen Probleme angesprochen. Bei der Visibility geht es dagegen darum, dass auch als atomar geltende Operationen nicht sofort sichtbar sind. Durch eigene CPU-Caches kann es dazu kommen, dass eine bereits aktualisierte Variable im Cache einer anderen CPU noch eine Weile den alten Wert enthält [2] (Abb. 2).

Abb. 2: Visibility: Atomare Operation ist nicht sofort sichtbar

Etwas seltener kommt es beim Ausführen oder beim Kompilieren der Befehle vor, dass CPU oder Just-in-time-Compiler von der Reihenfolge anders ausgeführt werden, als sie im Code stehen. Selbstverständlich sichern alle Teilnehmer zu, dass der ausführende Thread davon nichts mitbekommt. Es ist z. B. egal, in welcher Reihenfolge unabhängige Berechnungen ausgeführt werden, solange wir das Ergebnis nur am Ende der Reihenfolge konsumieren. Den Zugriff eines nebenläufigen Threads kann der JIT-Compiler erwartungsgemäß...

Java Magazin
Teil 2: Problemkategorien im Java Memory Model

Nebenläufigkeit im Zentrum

Die Spezifikation des Java Memory Model gehört zu der kompliziertesten im Java-Umfeld. Ihr Verständnis ist aber in Zeiten von Mehrkernprozessoren extrem wichtig, um Java-Applikationen zu schreiben, die Nebenläufigkeit korrekt unterstützen.

Rodion Alukhanov, Vadym Kazulkin


ArtikelserieTeil 1: Hardware-Memory-Modelle reloadedTeil 2: Problemkategorien im Java Memory ModelTeil 3: Das Open JDK Java Concurrency Stress Tests ToolTeil 4: Die Zukunft des Java Memory Models

Die Reihenfolge von Memory-Operationen wird oft durchmischt. Schuld daran sind Befehls-Reorderings in der CPU, Probleme bei der Synchronisierung von CPU und Cache, der Java-Compiler oder der Java-Just-in-time-Compiler. Die Kontrolle von Memory-Barrieren ist bei der Entwicklung in den meisten Fällen sehr komplex. Java-Spezifikationen vereinfachen das darunterliegende Hardware Memory Model. Das Java Memory Model wird in der Literatur über drei Problemkategorien [1] erklärt. Das sind: Atomicity, Visibility, Ordering.

Seit Java 1.0 ist es so, dass lediglich 32-Bit-Zuweisungen atomic stattfinden. Sollte ein Austausch von Informationen zwischen mehreren Threads komplexer sein, muss man die Blöcke synchronisieren. Lieblingsbeispiel ist dabei ein Inkrement (Abb. 1). Trotz der Kürze des Statements erfolgen im Hintergrund drei Operationen, die ohne Synchronisation zu unerwünschten Ergebnissen in einer Multi-Thread-Anwendung führen können.

Abb. 1: Blöcke synchronisieren

Die klassische Lösung, nicht nur in Java, ist die Benutzung von synchronisiertem Code. Durch das bereits in der ersten Java-Version eingeführte Schlüsselwort synchronized werden die Blöcke markiert, die nur von einem Thread ausgeführt werden dürfen. Auch die Möglichkeit mit dem Schlüsselwort volatile auf eine 64-Bit-Variable ohne Synchronisierung zuzugreifen, gehörte immer zum Java-Sprachumfang.

Bei Atomicity werden noch keine Multi-Core-spezifischen Probleme angesprochen. Bei der Visibility geht es dagegen darum, dass auch als atomar geltende Operationen nicht sofort sichtbar sind. Durch eigene CPU-Caches kann es dazu kommen, dass eine bereits aktualisierte Variable im Cache einer anderen CPU noch eine Weile den alten Wert enthält [2] (Abb. 2).

Abb. 2: Visibility: Atomare Operation ist nicht sofort sichtbar

Etwas seltener kommt es beim Ausführen oder beim Kompilieren der Befehle vor, dass CPU oder Just-in-time-Compiler von der Reihenfolge anders ausgeführt werden, als sie im Code stehen. Selbstverständlich sichern alle Teilnehmer zu, dass der ausführende Thread davon nichts mitbekommt. Es ist z. B. egal, in welcher Reihenfolge unabhängige Berechnungen ausgeführt werden, solange wir das Ergebnis nur am Ende der Reihenfolge konsumieren. Den Zugriff eines nebenläufigen Threads kann der JIT-Compiler erwartungsgemäß...

Neugierig geworden?


    
Loading...

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang