Geschichte des Themas:
Als Java gerade entstand, gab es keine formale Beschreibung darüber, wie Speicher und Threads miteinander interagieren. Das führte dazu, dass dieselben Programme sich auf verschiedenen JVMs unterschiedlich verhalten konnten, was zu schwer fassbaren Bugs führte. Im Jahr 2004 wurde das Java Memory Model (JMM) in die Java-Spezifikation aufgenommen, um klar zu definieren, wie Threads Updates von Variablen sehen.
Problem:
Ohne ein klares Speicher-Modell können Schreib- und Leseoperationen vom Compiler oder Prozessor vermischt werden, was zu unerwartetem Verhalten beim Zugriff auf gemeinsame Variablen aus verschiedenen Threads führt. Dies kann zu Race Conditions, Sichtbarkeitsproblemen und schwer zu debuggenden Synchronisationsfehlern führen.
Lösung:
Das JMM definiert Regeln dafür, wann Änderungen, die von einem Thread vorgenommen werden, für andere sichtbar werden. Es werden Konzepte wie happens-before, Synchronisatoren (synchronized, volatile, final), interne Sperren festgelegt und die Umstrukturierung von Anweisungen in einer Multithreading-Umgebung eingeschränkt.
Beispielcode:
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int get() { return count; } }
Schlüsselfunktionen:
Warum macht volatile Operationen nicht atomar, sondern gewährleistet nur Sichtbarkeit?
Volatile gewährleistet nur die Sichtbarkeit von Änderungen zwischen Threads, jedoch nicht die Atomarität der Änderung. Beispielsweise ist das Inkrementieren einer volatile-Variablen keine atomare Operation, da die Aktion aus Lesen, Ändern und Schreiben besteht.
Beispielcode:
volatile int count = 0; count++; // Nicht atomar!
Kann ein synchronized-Block die Sichtbarkeit von Änderungen außerhalb des Blocks garantieren?
Ja. Nach dem Verlassen eines synchronized-Blocks sind alle innerhalb vorgenommene Änderungen für andere Threads sichtbar, die in den Block für dasselbe Objekt-Monitor eintreten.
Wann werden Änderungen an final-Feldern für andere Threads sichtbar?
Final-Felder garantieren die korrekte Veröffentlichung nur, wenn das Objekt vollständig konstruiert ist, bevor Referenzen an andere Threads übergeben werden, beispielsweise durch unveränderliche Objekte. Andernfalls kann Sichtbarkeit eines nicht initialisierten Zustandes auftreten.
Negativer Fall
Ein Entwickler beschloss, den Zähler für Zugriffe auf die Website mit einem einfachen volatile Inkrement aus mehreren Threads zu erhöhen.
Vorteile:
Nachteile:
Positiver Fall
Verwendung von AtomicInteger für den Zähler oder synchronized-Methoden.
Vorteile:
Nachteile: