W3docs

Java Singleton-Pattern

Das Singleton-Pattern in Java sicher implementieren mit Eager-, Lazy- und Enum-basierten Ansätzen.

Das Singleton-Pattern schränkt eine Klasse auf eine einzige Instanz ein und bietet einen globalen Zugriffspunkt darauf. Ein Logging-Facade, eine Konfigurationsregistrierung, ein In-Process-Cache — das sind die Dinge, für die es geeignet ist. In Java gibt es für dieses Pattern einige Standardformen, jede mit ihren eigenen Thread-Safety- und Lazy-Loading-Kompromissen. Es gibt auch einen Ansatz, der die meisten Probleme still und leise umgeht.

Zunächst eine kurze Warnung. „Singleton" ist bekanntermaßen leicht zu überbeanspruchen — jeder Singleton ist im Grunde ein globaler Wert, und globale Werte machen den Code schwerer testbar und schwerer nachvollziehbar. Die meisten modernen Java-Anwendungen bevorzugen Dependency Injection: Das Framework richtet eine einzelne Instanz ein und gibt sie den Komponenten, die sie benötigen, ohne dass eine davon Foo.getInstance() aufrufen müsste. Greifen Sie auf einen expliziten Singleton zurück, wenn DI nicht verfügbar oder tatsächlich überdimensioniert ist.

Was jeder Singleton benötigt

Jede Singleton-Implementierung besteht aus drei Teilen:

  • Ein privater Konstruktor, damit niemand außerhalb der Klasse new aufrufen kann.
  • Ein privates statisches Feld, das die einzelne Instanz hält.
  • Ein öffentlicher statischer Accessor, der sie zurückgibt.

Die Variationen betreffen hauptsächlich wann die Instanz erstellt wird und wie der Zugriff thread-sicher bleibt.

Eager Initialization

Die einfachste Form erstellt die Instanz beim Laden der Klasse:

public final class Eager {
  private static final Eager INSTANCE = new Eager();
  private Eager() {}
  public static Eager getInstance() { return INSTANCE; }
}

Die Klasseninitialisierung in der JVM ist garantiert thread-sicher und läuft genau einmal, sodass INSTANCE ohne Locks sicher gesetzt wird. Verwenden Sie dies, wenn:

  • Die Konstruktion günstig ist oder Sie wissen, dass Sie die Instanz immer benötigen.
  • Sie die Kosten zum Zeitpunkt des Klassenladens in Kauf nehmen können.

Lazy Initialization mit Double-Checked Locking

Wenn die Konstruktion teuer ist und Sie die Instanz möglicherweise nie benötigen, können Sie sie verzögern. Die naive Lazy-Version ist nicht thread-sicher; die korrekte verwendet Double-Checked Locking mit volatile:

public final class Lazy {
  private static volatile Lazy instance;
  private Lazy() {}

  public static Lazy getInstance() {
    Lazy local = instance;       // local read avoids re-reading the volatile field
    if (local == null) {
      synchronized (Lazy.class) {
        local = instance;
        if (local == null) {
          local = new Lazy();
          instance = local;
        }
      }
    }
    return local;
  }
}

volatile ist unverzichtbar — ohne es könnte ein anderer Thread das Feld als nicht-null sehen, obwohl der Konstruktor noch nicht abgeschlossen ist. Umständlich, aber korrekt.

Der Initialization-on-Demand Holder

Die sauberste Lazy-Form verwendet eine private verschachtelte Klasse. Die JVM lädt den Holder nur, wenn jemand zum ersten Mal getInstance() aufruft, sodass die Arbeit verzögert wird — und die Klassen-Init-Garantien der JVM übernehmen die Thread-Safety:

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

Kein synchronized, kein volatile, kein Double-Checked Locking — und dennoch lazy. Dies ist die Lazy-Form, die in den meisten Fällen verwendet werden sollte.

Der Enum-Singleton

Der kürzeste korrekte Singleton in Java ist ein Enum mit einer Konstante:

public enum Config {
  INSTANCE;
  public String get(String key) { /* ... */ }
}

Config.INSTANCE ist der Singleton. Joshua Blochs Empfehlung (Effective Java, Item 3) lautet, dass dies die beste Singleton-Implementierung ist, da die JVM folgendes garantiert:

  • Genau eine Instanz. Enums werden pro JVM genau einmal konstruiert.
  • Thread-sichere Konstruktion. Dieselben Klassen-Init-Garantien wie beim Holder-Pattern.
  • Reflection-sicher. Reflection kann den Konstruktor eines Enums nicht aufrufen; gewöhnliche Singletons können durch Constructor.setAccessible(true) ausgehebelt werden.
  • Serialisierungssicher. Das Deserialisieren eines normalen Singletons kann stillschweigend eine zweite Instanz erzeugen, es sei denn, Sie behandeln readResolve. Enums sind dagegen immun.

Das Einzige, was es nicht kann, ist eine andere Klasse zu erweitern — Enums erweitern implizit java.lang.Enum. Sie können jedoch weiterhin Interfaces implementieren.

Dinge, die naive Singletons zerstören

Achten Sie auf diese — sie sind der Grund, warum „ein statisches Feld verwenden" nicht immer ausreicht:

  • Mehrere Klassenlader. Ein Singleton ist eine Instanz pro Classloader, nicht eine pro JVM. In Containern, die Apps mit eigenen Loadern isolieren, kann dieselbe Klasse mehrere „die" Instanzen haben.
  • Reflection. setAccessible(true) plus Constructor.newInstance() kann eine zweite Instanz eines beliebigen Nicht-Enum-Singletons erstellen. Schützen Sie den Konstruktor mit if (INSTANCE != null) throw ..., wenn dies ein echtes Anliegen ist.
  • Serialisierung. Ein Serializable-Singleton benötigt private Object readResolve() { return INSTANCE; }, um zu vermeiden, dass bei jeder Deserialisierung eine zweite Kopie erzeugt wird.
  • Testing. Singletons sind bekanntermaßen schwer zu stubben oder zurückzusetzen. Bevorzugen Sie Dependency Injection in Code, den Sie Unit-testen möchten.

Ein ausgearbeitetes Beispiel

java— editable, runs on the server

Was kommt als Nächstes

Damit schließt Teil 6 und die gesamte Einführung in objektorientiertes Java ab — von Klassen, Vererbung und Polymorphismus über Interfaces, Enums, Records, versiegelte Hierarchien bis hin zu den Methoden, die jedes Objekt erbt. Der nächste Teil befasst sich damit, wie Java-Code organisiert wird: Namensräume, das Dateisystem-Layout, das diese widerspiegelt, und die import-Mechanismen, die Typen aus anderen Orten in Ihren Code einbinden. Weiter zu Java Packages.

Übungen

Übung
Warum gilt der Enum-Singleton (`enum X { INSTANCE; ... }`) in der Regel als die beste Java-Singleton-Implementierung?
Warum gilt der Enum-Singleton (`enum X { INSTANCE; ... }`) in der Regel als die beste Java-Singleton-Implementierung?
Was this page helpful?