Java - von Rias A. Sherzad am Samstag, August 26, 2006 0:19 - 14 Kommentare
Singleton Pattern in Java
Während der Softwareentwicklung ist es manchmal notwendig, sicherzustellen, daß nur eine Instanz eines Objekts existiert. Dieser Fall tritt zum Beispiel auf, wenn Informationen in eine Protokolldatei geschrieben werden sollen. Um kontrollieren zu können, daß nur ein solches Objekt – und nicht mehrere parallel – in diese Datei schreiben, kann das Singleton-Pattern eingesetzt werden. Mit dem Singleton Pattern kann dafür gesorgt werden, daß nur eine Instanz des als Singleton implementierten Objekts existiert.
Allerdings gibt es hier noch eine Kleinigkeit zu beachten, die nicht jedem Entwickler bekannt ist: es existiert immer nur eine Instanz pro ClassLoader! In einer MultiThreading-Umgebung mit mehreren ClassLoadern, wie sie in ServletContainern üblich sind, ist es nicht unwahrscheinlich, daß das Objekt, welches als Singleton implementiert ist, mehrfach existiert. Wir werden die verschiedenen Implementierungen des Singleton Patterns durchgehen, und diese schrittweise zu einer möglichen Idealimplementierung ausbauen.
Zum leichteren Verständnis der Design Patterns möchte ich Ihnen den Bestseller Entwurfsmuster von Kopf bis Fuß nahelegen, welches die meiner Meinung nach verständlichste und beste Einführung in die Welt der Design Patterns bietet.
Kategorie
Das Singleton Pattern wird zu den „Creational Patterns“ gezählt, da es Objekte erzeugt und zurückliefert.
Einsatzbereich
Das Singleton Pattern kann eingesetzt werden, wenn:
- … sichergestellt werden soll, daß das betreffende Objekt nur einmal instanziiert wird
- … ein globaler Zugriffspunkt auf das Objekt existieren soll
- … die Instanziierung des Objekts es ermöglichen soll, ggf. in der Zukunft doch mehrere Instanzen zu instanziieren, ohne die Clients reimplementieren zu müssen
UML-Klassendiagramm: Singleton Pattern
Das UML-Klassendiagramm für das Singleton Pattern sieht folgendermaßen aus:
Das klassische Singleton
Die klassische Implementation eines Singleton verhindert die direkte Instanziierung des Objekts über den Standardkonstuktor, indem Sie für diesen den modifier private
verwendet. Damit jetzt aber auch außerhalb der Klasse eine Instanz angefordert werden kann, hat es sich „eingebürgert“, eine statische Methode namens getInstance()
bereitzustellen, die die einzige Instanz der Klasse zurückliefert. Diese einzige Instanz wiederum wird als statisches Attribut der Klasse gespeichert, die auch per private-modifier vor dem Zugriff außerhalb der Klasse geschützt wird. Die Implementation sieht damit folgendermaßen aus:
- package de.theserverside.designpatterns.singleton;
- /**
- * Klassische Implementierung des Singleton-Patterns
- */
- public class ClassicSingleton {
- private static ClassicSingleton instance = null;
- /**
- * Default-Konstruktor, der nicht außerhalb dieser Klasse
- * aufgerufen werden kann
- */
- private ClassicSingleton() {}
- /**
- * Statische Methode, liefert die einzige Instanz dieser
- * Klasse zurück
- */
- public static ClassicSingleton getInstance() {
- if (instance == null) {
- instance = new ClassicSingleton();
- }
- return instance;
- }
- }
Der obige Code implementiert das Konzept der lazy initialization, d.h. das Objekt wird erst instanziiert, wenn es erstmalig angefordert wird. Der Vorteil ist, daß, wenn das Singleton niemals verwendet wird, getInstance() also niemals aufgerufen wird, auch kein Speicher (und damit ggf. verbunde Resourcen) belegt wird.
Bei dieser Implementierung gibt es allerdings ein Problem: in single-thread Anwendungen funktioniert sie wunderbar, in Multithreadingumgebungen kann sie aber scheitern! Wenn von zwei Threads der erste getInstance()
aufruft, die Zuweisung instance = new ClassicSingleton()
noch nicht erreicht wurde, parallel der zweite Thread in diese Methode tritt und die Bedingung if (instance == null) {
erfüllt wird, dann werden zwei verschiedene Instanzen erzeugt. Damit wäre das Singleton-Konzept ausgehebelt und die Klasse nutzlos.
Synchronisierter Methodenzugriff
„Moment mal!“, werden Sie sagen, „Wieso synchronisieren wir nicht einfach den Zugriff auf die Methode?“. Nun gut, schauen wir uns einmal an, wie man das implementieren könnte:
- package de.theserverside.designpatterns.singleton;
- /**
- * Implementierung des Singleton-Patterns mit synchronisiertem
- * Methodenzugriff
- */
- public class SynchronizedSingleton {
- private static SynchronizedSingleton instance = null;
- /**
- * Default-Konstruktor, der nicht außerhalb dieser Klasse
- * aufgerufen werden kann
- */
- private SynchronizedSingleton() {}
- /**
- * Statische Methode, liefert die einzige Instanz dieser
- * Klasse zurück
- */
- public static synchronized SynchronizedSingleton getInstance() {
- if (instance == null) {
- instance = new SynchronizedSingleton();
- }
- return instance;
- }
- }
Ja, dieser Code ist tatsächlich thread-safe… das Problem ist allerdings, daß nur der erste Aufruf dieser Methode synchronized
sein muss, da fortlaufende Aufrufe dann direkt ein bereits instanziiertes Singleton zurückgeliefert bekommen. Methoden, die mit dem modifier synchronized
versehen sind, können auf älteren JVMs bis zu 100x langsamer laufen! In einer Singleton-Implementierung, die z.B. ständig Informationen in eine Datei loggt (und aus welchen Gründen auch immer das performante log4j-Paket nicht verwendet) kann dies die Geschwindigkeit der Anwendung drastisch reduzieren. Wir müssen uns also nach einer anderen Lösung umschauen…
Checked locking
Wie wäre es, wenn wir nur den kritischen Teil der Methode synchronisieren, also nur die Zeile mit der Instanziierung des Objekts samt Zuweisung an das Klassenattribut instance
? Damit würde doch eigentlich nur der erste Zugriff auf die Methode in einen synchronized
-Block laufen – und nicht die gesamte Methode bei jedem Aufruf – oder?
- package de.theserverside.designpatterns.singleton;
- /**
- * Implementierung des Singleton-Patterns mit synchronisiertem
- * Zugriff auf die Instanziierung und einfacher Bedingungsprüfung
- */
- public class CheckedLockingSingleton {
- private static CheckedLockingSingleton instance = null;
- /**
- * Default-Konstruktor, der nicht außerhalb dieser Klasse
- * aufgerufen werden kann
- */
- private CheckedLockingSingleton() {}
- /**
- * Statische Methode, liefert die einzige Instanz dieser
- * Klasse zurück
- */
- public static CheckedLockingSingleton getInstance() {
- if (instance == null) {
- synchronized(CheckedLockingSingleton.class) {
- instance = new CheckedLockingSingleton();
- }
- }
- return instance;
- }
- }
Auf den ersten Blick sieht es so aus, als würde das funktionieren. Aber jetzt stellen Sie sich das folgende Szenario vor: der erste Thread gelangt in den synchronized
-Block und erhält seinen Lock. Bevor allerdings das Singleton instanziiert und zugewiesen werden kann, übernimmt Thread 2 die Kontrolle und überwindet die if
-Bedingung. Thread 2 steht damit direkt vor dem synchronized
-Block und muss warten, bis Thread 1 den Lock freigibt. Thread 1 übernimmt nun wieder die Kontrolle und instanziiert das Singleton, weist es dem Klassenattribut instance
zu und liefert es per return instance
an die aufrufende Methode zurück. Wenn jetzt Thread 2 weiterlaufen darf, wird ein neues Objekt instanziiert und zurückgegeben. Damit ist dieser Code nicht thread-safe und sollte nicht verwendet werden.
Double-checked locking
An dieser Stelle ist ein Idiom entstanden, das als double-checked locking bezeichnet wird, welches den Zugriff auf die Instanziierung mittels einer zweiten Bedingung schützen soll. Das soll sicherstellen, daß die Instanziierung nur durchgeführt wird, wenn wirklich noch keine Instanz angelegt wurde. Die Implementierung sieht hier folgendermaßen aus:
- package de.theserverside.designpatterns.singleton;
- /**
- * Implementierung des Singleton-Patterns mit synchronisiertem
- * Zugriff auf die Instanziierung und zweifacher Bedingungsprüfung
- */
- public class DoubleCheckedLockingSingleton {
- private static DoubleCheckedLockingSingleton instance = null;
- /**
- * Default-Konstruktor, der nicht außerhalb dieser Klasse
- * aufgerufen werden kann
- */
- private DoubleCheckedLockingSingleton() {}
- /**
- * Statische Methode, liefert die einzige Instanz dieser
- * Klasse zurück
- */
- public static DoubleCheckedLockingSingleton getInstance() {
- if (instance == null) {
- synchronized(DoubleCheckedLockingSingleton.class) {
- if (instance == null) {
- instance = new DoubleCheckedLockingSingleton();
- }
- }
- }
- return instance;
- }
- }
Das könnte doch funktionieren… wäre da nicht das Speichermodell der Java-Plattform! Die zweite Bedingung if (instance == null)
in Zeile 23 kann nämlich zu true evaluieren, ohne daß new DoubleCheckedLockingSingleton()
aufgerufen, und das instanziierte Objekt dem Klassenattribut instance
zugewiesen wurde! Um das exemplarisch zu verdeutlichen, schauen wir uns den folgenden Pseudo-Bytecode für den Befehl instance = new DoubleCheckedLockingSingleton()
an:
- // Speicher alloziieren
- ptrMemory = allocateMemory()
- // Den Speicher dem Klassenattribut zuweisen, ab hier gilt: instance != null
- assignMemory(instance, ptrMemory)
- // Den Konstruktor aufrufen, das Objekt ist dann ab hier korrekt instanziiert
- callDoubleCheckedLockingSingletonConstructor(instance)
Zwischen der 4. und der 6. Zeile könnte die Ausführung des Threads durch die Java Virtual Machine unterbrochen, und die Ausführung eines zweiten Threads vorgezogen werden. Die zweite Bedingung if (instance == null)
würde damit zu true evaluieren, ohne daß bisher der Konstruktor (Zeile 6) aufgerufen worden wäre. Das double-checked locking ist damit nicht sicher.
Konstrukte wie diese sind in JIT-Compilern nicht unüblich und da man nicht immer voraussagen kann, wo der Code läuft sollte man das double-checked locking vermeiden, um auf der sicheren Seite zu stehen.
Eine Lösung muss her…
Nach all den Synchronisierungen, Bedingungen und Zuweisungen gibt es momentan nur eine Implementierung, die alle genannten Probleme löst: das Objekt muss beim erstmaligen Zugriff auf die Klasse durch die JVM instanziiert werden. Das erreichen wir, indem wir den Aufruf des Konstruktors schon bei der Deklaration des Klassenattributs instance
durchführen:
- package de.theserverside.designpatterns.singleton;
- /**
- * Performante und thread-safe Implementierung des Singleton-Patterns
- */
- public class Singleton {
- private static Singleton instance = new Singleton();
- /**
- * Default-Konstruktor, der nicht außerhalb dieser Klasse
- * aufgerufen werden kann
- */
- private Singleton() {}
- /**
- * Statische Methode, liefert die einzige Instanz dieser
- * Klasse zurück
- */
- public static Singleton getInstance() {
- return instance;
- }
- }
Mit dieser Lösung werden die Performanceprobleme, die mit eine Synchronisation mit sich bringt, eliminiert und es ist gewährleistet, daß das Objekt auch in einer Multithreadingumgebung stets nur einmal instanziiert wird.
Sourcecode zum Artikel
Sourcecode: Singleton Pattern
Weiterführende Literatur
Wer sich mit Design Patterns näher auseinandersetzen will, kommt um das Standardwerk der „Gang of Four“ (GoF) „Design Patterns“ nicht herum. Der Link führt zur englischen Ausgabe des Buches, da die deutsche Übersetzung sehr grausam ist…
Wenn Ihnen dieses Buch zu trocken ist und Sie leichter verdaubares Material suchen, sind die beiden folgenden Bücher zum Thema „Design Patterns“, bzw. „Design Patterns in Java“ auch sehr zu empfehlen.
Eine unterhaltsame und sehr leicht verständliche Einführung in die Thematik Design Patterns in Java finden Sie mit „Entwurfsmuster von Kopf bis Fuß„. Das gesamte Buch ist bebildert und anschaulich gestaltet, und die Konzepte werden didaktisch hervorragend erläutert. Die Codebeispiele sind in Java geschrieben und auf das Nötigste reduziert.
Das Buch „The Design Patterns Java Workbook“ arbeitet die Design Patterns der Reihe nach ab, aber nicht, ohne vorher die Grundlagen (Interfaces, Abstrakte Klassen etc.) zu erläutern. Jedes Kapitel enthält Aufgaben, die gelöst werden können und anhand derer das Erlernte sofort praktisch angewandt werden kann. Da bisher keine deutsche Übersetzung existiert (Stand: August 2006), müssen Sie noch mit der englischen Fassung vorlieb nehmen.
14 Kommentare
Florian Strauch
Der Artikel ist schön geschrieben.
Danke schön :-)
Die von Ihnen genannten Nachteile existieren in der Tat. Als das Singleton-Pattern in der Java-Welt seinen Einzug gefunden hat, waren Testing-Frameworks ? la JUnit nicht/kaum existent, so – und das ist meine Interpretation – daß das automatisierte Testing im Hintergrund stand.
Das Inversion of Control Pattern hingegen gibt es erst seit wenigen Jahren (2-3?) und kann einige der Probleme lösen. IoC wird auch Thema einer meiner (oder Ihrer? :-)) nächsten Artikel sein.
nitram
Der Artikel ist wirklich gut geschrieben, hat nur einen kleinen Haken: das double checked locking idiom gilt als „gebrochen“.
Ja, das sagt der Artikel aber auch…
Singleton in Java — ingokallenbach.de
[…] Artikel Singleton Pattern in Java beschreibt einige Implementierungen des Singletons in Java, bevor am Ende auf die bisher oft […]
E.L.
Ein guter Artikel.
Anmerkungen zu „Double-checked locking“:
1) mit dem Speichermodell der Java-Plattform hat das nichts zu tun. Das ist einfach so allgemein möglich, dass es zunächst der Referenz die Adresse des allozierten Speicher zugewiesen und erst danach der Konstruktor aufgerufen wird.
2) statt
„Die zweite Bedingung if (instance == null) in Zeile 23 kann nämlich zu true evaluieren, ohne daß new DoubleCheckedLockingSingleton() aufgerufen, und das instanziierte Objekt dem Klassenattribut instance zugewiesen wurde!“
sollte es ungefähr so heißen :
„Die *erste* Bedingung if (instance == null) in Zeile *21* kann nämlich im zweiten Thread zu true evaluieren, *bevor* der Konstruktor DoubleCheckedLockingSingleton() im ersten Thread aufgerufen wird. In diesem Fall wird im zweiten Thread mit einem nicht (korrekt) initialisierten „Objekt“ gearbeitet!“
Vielen Dank für den Beitrag!
Tatsächlich ist der Artikel in seinen technischen Details an einigen Stellen wahrscheinlich nicht mehr auf dem aktuellen Stand, bzw. ungenau oder sogar fehlerhaft. Sobald ich die Zeit finde werde ich das Feedback einbauen, auch das aus mir zugesandten E-Mails.
Danke nochmals!
E.L.
und noch etwas weiter:
3) statt „Zwischen der zweiten und der vierten Zeile …“ sollte es sein „Zwischen der 4. und der 6. Zeile …“
E.L.
Gern geschehen :)
und noch etwas weiter:
3) statt “Zwischen der zweiten und der vierten Zeile …” sollte es sein “Zwischen der 4. und der 6. Zeile …”
Das war einfach, erledigt – thanks :-)
[…] den Quellcode dazu). 3. Wie Akeshihiro aiuch sagte, k?nnte dir ein Singleton ebenfalls helfen: http://theserverside.de/singleton-pattern-in-java/ __________________ Handlanger des Dr. Ex1tus Ich kenne Matzes dunkles Geheimnis ! oO o L_/ OL […]
wlz
In Bezug auf den Satz:
„Die zweite Bedingung if (instance == null) würde damit zu true evaluieren, ohne daß bisher der Konstruktor (Zeile 6) aufgerufen worden wäre. Das double-checked locking ist damit nicht sicher.“
Da ich etwas verwirrt von dem komplizierten Satz war, und der Kommentar vom E.L auch nicht ganz korrekt ist, hier meine Version:
“Die *erste* Bedingung if (instance == null) in Zeile *21* kann nämlich im Thread B zu „FALSE“ evaluieren, *bevor* der Konstruktor DoubleCheckedLockingSingleton() im Thread A fertig wird (und somit eine Instanz die zwar nicht null ist, aber noch nicht abgeschlossen-initialisiert wurde zurückgeben). In diesem Fall wird im zweiten Thread mit einem nicht (abgeschlossen-initialisierten “Objekt” gearbeitet!”
Und entsprechend den Satz:
„Die zweite Bedingung if (instance == null) würde damit zu true evaluieren, ohne daß bisher der Konstruktor (Zeile 6) aufgerufen worden wäre.“
ändern in den
“Die *erste* Bedingung if (instance == null) in Zeile *21* kann nämlich im Thread B zu FALSE evaluieren, *bevor* der Konstruktor DoubleCheckedLockingSingleton() im Thread A abgeschlossen wird. In diesem Fall wird im Thread B mit einem nicht abgeschlossen-initialisierten “Objekt” gearbeitet!”
Singleton-Pattern f?r Datenbankverbindung - Forum Fachinformatiker.de
[…] dir mal den link hier an Singleton Pattern in Java da stehen all die probleme drinne. habe jetzt das objekt beim ersten zugriff auf die klasse durch […]
xtin
I think the following should work. It’s effectively the same as the double-checked locking but prevents the caveat of half-initialised objects by releasing the if-condition only when the object is fully created.
- /**
- * Implementierung des Singleton-Patterns mit synchronisiertem
- * Zugriff auf die Instanziierung und einfacher Bedingungsprüfung
- */
- public class VerySingleton {
- private static VerySingleton instance = null;
- private static Boolean created = false;
- /**
- * Default-Konstruktor, der nicht außerhalb dieser Klasse
- * aufgerufen werden kann
- */
- private VerySingleton() {}
- /**
- * Statische Methode, liefert die einzige Instanz dieser
- * Klasse zurück
- */
- public static VerySingleton getInstance() {
- if (!created) {
- synchronized(VerySingleton.class) {
- if(!created)
- instance = new VerySingleton();
- created = true;
- }
- }
- return instance;
- }
- }
Kommentieren
Weitere Empfehlungen:
Java - Jul 19, 2011 20:30 - 0 Kommentare
Eine (wirklich gute) Einführung in Maven
Mehr Artikel der Kategorie Softwarearchitekturen
- Decorator Pattern in Java
- Cloud Computing mit Google App Engine und Java
- JMS mit Oracle Advanced Queueing
- Buchempfehlung: Service-orientierte Architekturen mit Web Services. Konzepte – Standards – Praxis
- Einführung in SOA – Serviceorientierte Architekturen
Datenbanken, Internet Technologien - Nov 19, 2009 9:24 - 0 Kommentare
Oracle WebServices im praktischen Einsatz
More In Datenbanken
- JMS mit Oracle Advanced Queueing
- Eine kurze Einführung in db4o
- Native Kompilierung von PL/SQL-Routinen in Oracle 10g
Der Artikel ist schön geschieben. Die Umsetzung des Singleton Pattern in dieser – klassischen – Form bringt aber mehr Nachteile mit sich, die angesprochen werden sollten.
Eigentlich ist ein Sigleton als Objekt-Muster gedacht. Die dargestellte Implementierung stellt zwar sicher, dass es nur eine Instanz der Klasse geben kann, aber als Nebeneffekt ist diese Instanz damit auch global verfügbar.
Eberhard Wolf schreibt in Vom Pattern zum Anti-Pattern: