Singleton
Einführung
In vielen Anwendungen müssen mehrere Programmteile dieselben Ressourcen nutzen. Ein Logger soll zentrale Log-Dateien schreiben, eine Datenbankverbindung soll für alle Abfragen dienen und Konfigurationen müssen konsistent verfügbar sein.

Wenn jede Klasse ihre eigene Instanz dieser Ressourcen erzeugt, entstehen unnötige Speicherlast, doppelte Zugriffe und Fehlerquellen.
Das Singleton-Pattern löst dieses Problem, indem es sicherstellt, dass eine Klasse nur eine einzige Instanz besitzt, die von allen genutzt wird.
In dieser Lerneinheit erfährst du, wie das Muster funktioniert, warum es so verbreitet ist und in welchen Szenarien es dir hilft, konsistente und effiziente Software zu entwickeln.
Lernziele
Nach dieser Lerneinheit kannst du:
- Das Singleton-Pattern erklären
- Das Singleton-Pattern praktisch anwenden
- Vorteile und Nachteile bewerten
- Einsatzszenarien beurteilen und Alternativen benennen
Überleitung
Das Singleton-Pattern ist ein Entwurfsmuster aus der objektorientierten Programmierung. Es stellt sicher, dass eine Klasse nur eine einzige Instanz besitzt und dass diese Instanz global verfügbar ist. Dadurch kannst du zentrale Ressourcen in einer Anwendung effizient verwalten.
Schauen wir uns das Singleton-Pattern genauer an.
Was ist das Singleton-Pattern?
Das Singleton-Pattern sorgt dafür, dass:
- nur ein Objekt einer bestimmten Klasse erzeugt wird,
- dieses Objekt von überall im Programm zugänglich ist,
- und die Verwaltung dieser Instanz kontrolliert abläuft.

Singleton-Pattern (Bildrechte: Ausbildung in der IT)
Ein Beispiel ist ein Logger, der alle Log-Nachrichten zentral verwaltet. Es wäre ineffizient, für jede Klasse eigene Logger-Instanzen zu erzeugen. Stattdessen nutzen alle Programmteile dieselbe Instanz.
Warum brauchen wir das Singleton-Pattern?
Das Singleton wird eingesetzt, wenn du sicherstellen musst, dass eine zentrale Ressource nur einmal existiert:
- Logger: Alle Programmteile schreiben in dieselbe Log-Datei.
- Datenbankverbindung: Eine einzige Verbindung wird für die gesamte Anwendung genutzt.
- Konfigurationsmanager: Einstellungen werden zentral verwaltet.
- Cache: Ein gemeinsamer Zwischenspeicher wird von allen Komponenten verwendet.
Dadurch sparst du Speicher, verhinderst Inkonsistenzen und stellst sicher, dass alle Teile der Anwendung mit denselben Daten arbeiten.
Die drei Grundregeln
Das Singleton folgt drei einfachen Regeln:
- Der Konstruktor ist privat und somit nur innerhalb der Klasse aufrufbar.
- Die Klasse hält eine statische Variable, in der die Instanz gespeichert wird.
- Eine Methode getInstance() sorgt dafür, dass immer dieselbe Instanz zurückgegeben wird.
Implementierung des Singleton-Patterns
Einfache Implementierung in Java
public class Logger {
// 1. Statische Variable für die einzige Instanz
private static Logger instance = null;
// 2. Privater Konstruktor - keine Objekterzeugung von außen
private Logger() {
// Initialisierung hier
}
// 3. Öffentliche Methode für den Zugriff auf die Instanz
public static Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}
// Normale Methoden der Klasse
public void log(String message) {
System.out.println("[LOG] " + message);
}
}Erklärung des Codes
Dieser Java-Code implementiert das Singleton-Entwurfsmuster (Singleton Pattern). Das Ziel dieses Musters ist es, sicherzustellen, dass von einer Klasse nur ein einziges Objekt (eine “Instanz”) im gesamten Programm existieren kann.
Das wird durch drei Schritte erreicht:
-
private static Logger instance = null;Dies ist eine Variable, die der Klasse selbst gehört (dankstatic) und nicht einem Objekt. Sie speichert die einzige Instanz, die jemals erstellt wird. Sie startet alsnull(leer). -
private Logger()Der Konstruktor (die Funktion, die mitnewaufgerufen wird) istprivate. Das ist der wichtigste Trick: Es verhindert, dass Code außerhalb dieser Klassenew Logger()aufrufen und neue Objekte erstellen kann. -
public static Logger getInstance()Dies ist die einzige öffentliche Methode, um an den Logger zu kommen.- Sie prüft (
if), ob dieinstance-Variable nochnull(leer) ist. - Nur beim allerersten Aufruf ist sie
null, und nur dann wird ein neuesLogger-Objekt erstellt (das darf diese Methode, da sie Teil der Klasse ist). - Bei allen folgenden Aufrufen ist
instancenicht mehrnull, und es wird einfach das bereits existierende Objekt zurückgegeben.
- Sie prüft (
Immer, wenn jemand Logger.getInstance().log("...") aufruft, wird garantiert derselbe Logger verwendet.
Implementierung des Singleton-Patterns
In deinem Hauptprogramm kannst du den Logger so nutzen:
public class MeinProgramm {
public static void main(String[] args) {
// Hol dir die Logger-Instanz
Logger logger = Logger.getInstance();
logger.log("Programm gestartet");
// An einer anderen Stelle im Code
Logger derselbeLogger = Logger.getInstance();
derselbeLogger.log("Das ist derselbe Logger!");
// Beweis: Beide Referenzen zeigen auf dasselbe Objekt
System.out.println(logger == derselbeLogger); // true
}
}Erklärung des Codes
Dieser Code demonstriert die Nutzung des Logger-Singletons (aus dem vorigen Beispiel) in einem Programm.
- Erster Zugriff:
Logger logger = Logger.getInstance();ruft die statische Methode auf, um die einzige Instanz des Loggers zu erhalten und speichert sie in der Variablelogger. - Zweiter Zugriff:
Logger derselbeLogger = Logger.getInstance();simuliert einen Zugriff an einer anderen Stelle im Code. Es ruft dieselbe Methode erneut auf. - Das Ergebnis: Wegen der Logik im Singleton (der
if (instance == null)-Prüfung) gibtgetInstance()beim zweiten Mal kein neues Objekt zurück, sondern die bereits existierende Instanz. - Der Beweis:
System.out.println(logger == derselbeLogger);vergleicht, ob beide Variablen (loggerundderselbeLogger) auf exakt dasselbe Objekt im Speicher zeigen. Das Ergebnis isttrue(wahr).
Implementierung des Singleton-Patterns
Thread-sichere Variante
In echten Anwendungen laufen oft mehrere Threads gleichzeitig. Um Konflikte zu verhindern, kannst du synchronized verwenden:
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance = null;
private ThreadSafeSingleton() {}
// synchronized verhindert Probleme bei gleichzeitigem Zugriff
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}Erklärung des Codes
Dies ist eine “Thread-sichere” Version des Singleton-Musters. Sie löst ein potenzielles Problem, das bei gleichzeitigem Zugriff durch mehrere “Threads” (parallele Ausführungen) entstehen kann.
-
Das Problem (Race Condition): Stell dir vor, zwei Threads rufen
getInstance()exakt gleichzeitig auf, wenninstancenochnullist.- Thread A prüft:
instance == null? (Ja) - Thread B prüft:
instance == null? (Ja) - Thread A erstellt ein Objekt:
instance = new ThreadSafeSingleton(). - Thread B erstellt ebenfalls ein Objekt:
instance = new ThreadSafeSingleton(). - Ergebnis: Es wurden zwei Objekte erstellt, was die Singleton-Regel bricht.
- Thread A prüft:
-
Die Lösung (
synchronized): Das Schlüsselwortsynchronizedmacht diegetInstance()-Methode zu einer “Engstelle”. Es funktioniert wie ein Schloss an einer Tür:- Nur ein Thread darf die Methode zu einem Zeitpunkt betreten.
- Wenn Thread A die Methode betritt, muss Thread B warten, bis Thread A fertig ist.
- Dadurch wird sichergestellt, dass die
if-Prüfung und die Objekterstellung als ununterbrochene Einheit (atomar) stattfinden und das Objekt garantiert nur einmal erstellt wird.
Praxisbeispiel: Konfigurationsmanager
In vielen Anwendungen werden Konfigurationswerte wie Datenbank-URLs oder App-Versionen benötigt. Damit diese Informationen nur einmalig geladen und zentral verfügbar sind, eignet sich das Singleton-Pattern.
public class ConfigManager {
private static ConfigManager instance = null;
private String databaseUrl;
private String appVersion;
private ConfigManager() {
loadConfiguration();
}
public static synchronized ConfigManager getInstance() {
if (instance == null) {
instance = new ConfigManager();
}
return instance;
}
private void loadConfiguration() {
this.databaseUrl = "jdbc:mysql://localhost:3306/myapp";
this.appVersion = "1.0.0";
}
public String getDatabaseUrl() {
return databaseUrl;
}
public String getAppVersion() {
return appVersion;
}
}
public class MeineAnwendung {
public void start() {
ConfigManager config = ConfigManager.getInstance();
System.out.println("Verbinde mit: " + config.getDatabaseUrl());
System.out.println("App-Version: " + config.getAppVersion());
}
}Erklärung des Codes
Dieser Code zeigt eine sehr praktische Anwendung des Singleton-Musters: einen zentralen Konfigurations-Manager.
-
ConfigManager(Das Singleton):- Die Klasse ist als (Thread-sicheres) Singleton aufgebaut (
getInstance(),private ConfigManager()). - Der Clou: Sobald das eine Objekt erstellt wird (beim ersten Aufruf von
getInstance()), ruft der private Konstruktor dieloadConfiguration()-Methode auf. loadConfiguration(): Diese Methode (hier simuliert) lädt die Konfigurationsdaten (z.B. aus einer Datei) und speichert sie in den internen Variablen (databaseUrl,appVersion). Dies passiert nur einmal.get...()-Methoden: Die Klasse bietet “Getter”-Methoden, damit der Rest der Anwendung diese geladenen Werte lesen, aber nicht verändern kann.
- Die Klasse ist als (Thread-sicheres) Singleton aufgebaut (
-
MeineAnwendung(Der Nutzer):- Diese Klasse zeigt, wie einfach es ist, auf die Konfiguration zuzugreifen.
- Egal wo im Code,
ConfigManager.getInstance()holt sich immer dasselbe Objekt, das die bereits geladenen Konfigurationsdaten enthält. - Anschließend werden die Getter (
getDatabaseUrl()) verwendet, um die benötigten Werte abzufragen.
Vorteile des Singleton-Patterns
Bevor du das Muster einsetzt, solltest du die Stärken kennen:
- Kontrollierte Instanziierung: Garantiert nur eine Instanz, verhindert versehentliche Mehrfacherzeugung.
- Globaler Zugriff: Die Instanz ist von überall erreichbar, ohne komplizierte Übergabe.
- Speicher-effizient: Spart Ressourcen, da nur eine Instanz Speicher belegt.
- Lazy Loading: Das Objekt wird erst erstellt, wenn es wirklich benötigt wird.
Nachteile und Risiken
Das Singleton-Pattern hat auch problematische Seiten:
- Testbarkeit: Globale Objekte sind schwer zu isolieren, Mocking ist kompliziert.
- Versteckte Abhängigkeiten: Nicht immer sofort sichtbar, welche Klassen das Singleton nutzen.
- Globaler Zustand: Unerwartetes Verhalten möglich, wenn mehrere Teile der Anwendung dieselben Daten verändern.
- Threading-Probleme: Ohne Synchronisation entstehen Race Conditions in Multithreading-Umgebungen.
Wann solltest du das Singleton-Pattern verwenden?
Verwende es, wenn:
- nur eine Instanz benötigt wird (z. B. Logger, Cache, Konfigurationsmanager),
- ein globaler Zustand erforderlich ist,
- die Instanzerzeugung ressourcenintensiv ist,
- du einen zentralen Zugriffspunkt brauchst.
Vermeide es, wenn:
- du in Zukunft mehrere Instanzen brauchst,
- das Objekt keinen globalen Zustand hat,
- du verschiedene Implementierungen flexibel austauschen möchtest,
- Testbarkeit für dein Projekt besonders wichtig ist.
Alternativen zum Singleton-Pattern
Falls das Singleton unpraktisch ist, gibt es erprobte Alternativen:
- Dependency Injection: Du übergibst die benötigten Objekte von außen, anstatt sie global verfügbar zu machen.
public class MeineKlasse {
private Logger logger;
public MeineKlasse(Logger logger) {
this.logger = logger;
}
}Erklärung des Codes
Dieser Code zeigt das Prinzip der Dependency Injection (Einreichen von Abhängigkeiten) und ist eine Alternative zur direkten Nutzung eines Singletons (Logger.getInstance()).
private Logger logger;: Die Klasse deklariert, dass sie eine Variable vom TypLoggerbenötigt, um zu arbeiten.public MeineKlasse(Logger logger): Dies ist der Konstruktor. Statt dass die Klasse selbst den Logger holt (z.B. perLogger.getInstance()), verlangt sie, dass ihr ein fertigesLogger-Objekt beim Erstellen von außen übergeben wird.this.logger = logger;: Das übergebene Logger-Objekt wird in der internen Variable der Klasse gespeichert, damit andere Methoden inMeineKlassees verwenden können.
Der Vorteil ist, dass MeineKlasse nicht mehr fest an die eine Singleton-Instanz gebunden ist. Man könnte ihr beim Testen z.B. einen “Fake”-Logger (Mock) übergeben.
Häufige Fehler vermeiden
- Singleton überall einsetzen: Nicht jedes Objekt ist ein Singleton-Kandidat (z. B. Benutzer).
- Thread-Safety ignorieren: In Multithreading-Umgebungen ohne Synchronisation können Race Conditions auftreten.
- Konstruktor nicht privat machen: Wird der Konstruktor nicht privat deklariert, können externe Klassen weitere Instanzen erzeugen.
Zusammenfassung
Zusammenfassung:
Grundlagen des Singleton-Patterns
Das Singleton-Pattern ist ein Entwurfsmuster, das sicherstellt, dass eine Klasse nur eine Instanz besitzt und diese global zugänglich macht.
- Verhindert Mehrfachinstanziierungen durch einen privaten Konstruktor.
- Verwaltet eine statische Variable für die einzige Instanz.
- Stellt eine öffentliche Zugriffsmethode (
getInstance()) bereit. - Häufige Anwendungsfälle: Logger, Datenbankverbindungen, Konfigurationsmanager, Cache.
Implementierung in Java
Eine einfache Implementierung basiert auf einer statischen Instanzvariable und einer Zugriffsmethode.
private static <Klasse> instance = null;speichert die Instanz.private <Klasse>() {}verhindert externe Objekterstellung.public static <Klasse> getInstance()erzeugt die Instanz bei Bedarf (Lazy Loading).- Für Multithreading wird die Methode mit
synchronizedabgesichert.
Praxisbeispiel: Konfigurationsmanager
Ein ConfigManager lädt Konfigurationen (z. B. Datenbank-URL, Version) einmalig und stellt sie global zur Verfügung.
- Vorteil: Einheitliche Konfiguration für alle Programmteile.
- Nutzung:
ConfigManager config = ConfigManager.getInstance();
Vorteile
- Kontrollierte Instanziierung: Nur eine Instanz existiert.
- Globaler Zugriff: Einfach aus allen Teilen des Programms verwendbar.
- Speicher-effizient: Spart Ressourcen bei aufwendigen Objekten.
- Lazy Loading: Erzeugung erst bei Bedarf.
Nachteile und Risiken
- Schwierige Testbarkeit: Globale Objekte sind schwer zu isolieren, Mocking kompliziert.
- Versteckte Abhängigkeiten: Nutzung nicht immer transparent.
- Globaler Zustand: Unterschiedliche Programmteile beeinflussen sich gegenseitig.
- Threading-Probleme: Ohne Synchronisation drohen Race Conditions.
Wann einsetzen und wann vermeiden?
Verwenden, wenn:
- nur eine Instanz benötigt wird (Logger, Cache, zentrale Konfiguration).
- ein globaler Zustand erforderlich ist.
- die Instanzerstellung aufwendig ist.
Vermeiden, wenn:
- in Zukunft mehrere Instanzen nötig sein könnten.
- kein globaler Zustand gebraucht wird.
- flexible Implementierungen erforderlich sind.
- Testbarkeit eine hohe Priorität hat.
Alternativen zum Singleton-Pattern
- Dependency Injection: Instanz wird als Parameter übergeben, keine globale Abhängigkeit.
Häufige Fehler
- Singleton inflationär einsetzen.
- Thread-Sicherheit ignorieren.
- Konstruktor nicht privat machen, wodurch mehrere Instanzen entstehen können.
Ausblick:
In der nächsten Lerneinheit beschäftigst du dich mit der Fabrikmethode (Factory Method) – einem wichtigen Entwurfsmuster zur flexiblen Objekterzeugung. Du lernst, wie du mit diesem Ansatz Code entkoppelst, Abhängigkeiten reduzierst und neue Klassen einfacher einführen kannst, ohne bestehende Strukturen zu verändern.