Programmierung

Speichermodell

Ein Speichermodell beschreibt, welche Ausführungsfolgen für ein Programm erlaubt sind, insbesondere bei nebenläufigen Lesezugriffen auf Variabeln. Das Java Memory Model wird in Kapitel 17 der Java Language Specification (JLS) beschrieben. Mit dem Java Specification Request 133 (JSR-133) wurden einige Probleme des ursprünglichen Modells adressiert. Die 2004 gefundenen Verbesserungen sind in die dritte Ausgabe der JLS (Third Edition) eingegangen und wurden mit Release 5 der Sprache realisiert. Die beiden wichtigsten Änderungen betreffen die Schlüsselwörter final und volatile.

Ein beliebtes Diskussionsthema unter Java-Entwicklern ist das Idiom der doppelten Überprüfung beim Zugriff auf ein Einzelstück (Singleton), ein nur einmal zu erzeugendes Objekt – einmal ohne, einmal mit Sperrung (double-checked locking). Da der Erwerb eines Monitors in Bezug auf die Ausführungsleistung teuer ist, zielt das Idiom darauf, diesen nur dann anzufordern, wenn die Instanz noch nicht initialisiert wurde, und sichergestellt werden muss, dass auch in einer nebenläufigen Situation nicht zur gleichen Zeit ein zweites Objekt erzeugt wird.

class Example {
    private volatile Helper helper;
    public Helper getHelper() {
        if (helper == null) {
            synchronized (this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }
}
class Helper {
}

Vor Java 5 war das Idiom problematisch – was ihm seine Berühmtheit verschaffte – weil die virtuelle Maschine Umordnungen von Speicherzugriffen vornehmen kann, etwa um redundante Zugriffe zu eliminieren. Die neue Bedeutung des Schlüsselwortes volatile verbietet jedoch eine Umordnung des Schreibzugriffs auf ein damit gekennzeichnetes Instanzfeld, und zwar ausdrücklich auch mit allen anderen Schreibzugriffen, die diesem in der Programmreihenfolge vorausgehen. Eben dies ermöglicht, dass die „moderne” Java-Variante des Idioms funktioniert.

Es soll nicht unerwähnt bleiben, dass es je nach Ausprägung (Klassenfeld, Instanzfeld, Vermeidung zyklischer Initialisierung) bessere Lösungen für die Initialisierung bei Bedarf gibt – die u.a. in Item 71 von Effective Java, 2. Auflage, von Joshua Bloch erörtert werden – etwa die Verwendung einer Behälterklasse.

class Helper {
    private static class LazyHolder {
        static final Helper INSTANCE = new Helper();
    }
    public static final Helper getInstance() {
        return LazyHolder.INSTANCE;
    }
}

Dieses Idiom nutzt aus, dass eine Klasse erst initialisiert wird, wenn sie benutzt wird (s.a. JLS, Kapitel 12, Abschnitt 4). Seine Eleganz liegt darin, dass keine Synchronisierung benötigt wird.

Eine andere wichtige Verbesserung des mit Java 5 eingeführten neuen Speichermodells sind die erweiterten Regeln zum Schlüsselwort final (JLS, Kapitel 17, Abschnitt 5). Über die Grundbedeutung hinaus, dass mit final gekennzeichnete Felder nicht geändert werden können, garantieren die revidierten Regeln für entsprechend den Regeln konstruierte Objekte, dass mit final gekennzeichnete Instanzfelder erst nach Beendigung des Konstruktors sichtbar werden. Ein Konstruktor entspricht den Regeln, wenn innerhalb desselben keine Referenz auf die gerade erzeugte Instanz weitergegeben wird.

Besitzt ein Objekt ein Instanzfeld, dessen Wert sich im Lebenszyklus des Objektes ändern kann, müssen Zugriffe auf den Status des Objektes synchronisiert werden, um eine konsistente Sicht auf das Objekt zu gewährleisten. Dies betrifft auch die Erzeugung eines solchen Objektes, denn Instanzfelder, die nicht als final gekennzeichnet sind, können ohne geeignete Synchronisierung von einem zweiten Ausführungsfaden in einem nicht oder noch nicht vollständig initialisierten Zustand gesehen werden.

5. Juli 2011 von Kai Yves Linden
Kategorien: Programmierung | Schlagwörter: , | Kommentare deaktiviert für Speichermodell