Java - von 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:

UML-Modell: Singleton Pattern

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:

  1. package de.theserverside.designpatterns.singleton;
  2.  
  3. /**
  4. * Klassische Implementierung des Singleton-Patterns
  5. */
  6. public class ClassicSingleton {
  7. private static ClassicSingleton instance = null;
  8.  
  9. /**
  10. * Default-Konstruktor, der nicht außerhalb dieser Klasse
  11. * aufgerufen werden kann
  12. */
  13. private ClassicSingleton() {}
  14.  
  15. /**
  16. * Statische Methode, liefert die einzige Instanz dieser
  17. * Klasse zurück
  18. */
  19. public static ClassicSingleton getInstance() {
  20. if (instance == null) {
  21. instance = new ClassicSingleton();
  22. }
  23. return instance;
  24. }
  25. }

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:

  1. package de.theserverside.designpatterns.singleton;
  2.  
  3. /**
  4. * Implementierung des Singleton-Patterns mit synchronisiertem
  5. * Methodenzugriff
  6. */
  7. public class SynchronizedSingleton {
  8. private static SynchronizedSingleton instance = null;
  9.  
  10. /**
  11. * Default-Konstruktor, der nicht außerhalb dieser Klasse
  12. * aufgerufen werden kann
  13. */
  14. private SynchronizedSingleton() {}
  15.  
  16. /**
  17. * Statische Methode, liefert die einzige Instanz dieser
  18. * Klasse zurück
  19. */
  20. public static synchronized SynchronizedSingleton getInstance() {
  21. if (instance == null) {
  22. instance = new SynchronizedSingleton();
  23. }
  24. return instance;
  25. }
  26. }

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?

  1. package de.theserverside.designpatterns.singleton;
  2.  
  3. /**
  4. * Implementierung des Singleton-Patterns mit synchronisiertem
  5. * Zugriff auf die Instanziierung und einfacher Bedingungsprüfung
  6. */
  7. public class CheckedLockingSingleton {
  8. private static CheckedLockingSingleton instance = null;
  9.  
  10. /**
  11. * Default-Konstruktor, der nicht außerhalb dieser Klasse
  12. * aufgerufen werden kann
  13. */
  14. private CheckedLockingSingleton() {}
  15.  
  16. /**
  17. * Statische Methode, liefert die einzige Instanz dieser
  18. * Klasse zurück
  19. */
  20. public static CheckedLockingSingleton getInstance() {
  21. if (instance == null) {
  22. synchronized(CheckedLockingSingleton.class) {
  23. instance = new CheckedLockingSingleton();
  24. }
  25. }
  26. return instance;
  27. }
  28. }

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:

  1. package de.theserverside.designpatterns.singleton;
  2.  
  3. /**
  4. * Implementierung des Singleton-Patterns mit synchronisiertem
  5. * Zugriff auf die Instanziierung und zweifacher Bedingungsprüfung
  6. */
  7. public class DoubleCheckedLockingSingleton {
  8. private static DoubleCheckedLockingSingleton instance = null;
  9.  
  10. /**
  11. * Default-Konstruktor, der nicht außerhalb dieser Klasse
  12. * aufgerufen werden kann
  13. */
  14. private DoubleCheckedLockingSingleton() {}
  15.  
  16. /**
  17. * Statische Methode, liefert die einzige Instanz dieser
  18. * Klasse zurück
  19. */
  20. public static DoubleCheckedLockingSingleton getInstance() {
  21. if (instance == null) {
  22. synchronized(DoubleCheckedLockingSingleton.class) {
  23. if (instance == null) {
  24. instance = new DoubleCheckedLockingSingleton();
  25. }
  26. }
  27. }
  28. return instance;
  29. }
  30. }

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:

  1. // Speicher alloziieren
  2. ptrMemory = allocateMemory()
  3. // Den Speicher dem Klassenattribut zuweisen, ab hier gilt: instance != null
  4. assignMemory(instance, ptrMemory)
  5. // Den Konstruktor aufrufen, das Objekt ist dann ab hier korrekt instanziiert
  6. 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:

  1. package de.theserverside.designpatterns.singleton;
  2.  
  3. /**
  4. * Performante und thread-safe Implementierung des Singleton-Patterns
  5. */
  6. public class Singleton {
  7. private static Singleton instance = new Singleton();
  8.  
  9. /**
  10. * Default-Konstruktor, der nicht außerhalb dieser Klasse
  11. * aufgerufen werden kann
  12. */
  13. private Singleton() {}
  14.  
  15. /**
  16. * Statische Methode, liefert die einzige Instanz dieser
  17. * Klasse zurück
  18. */
  19. public static Singleton getInstance() {
  20. return instance;
  21. }
  22. }

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

Sie können Kommentare zu diesem Artikel über den RSS 2.0-Feed nachverfolgen. Sie können einen Kommentar hinterlassen, oder einen Trackback von Ihrer eigenen Website setzen.

Florian Strauch
Sep 1, 2006 13:56

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:

Wenn das Singleton dann noch in anderen Klassen direkt im Code oder versteckt in Feld-Initialisierungen oder Konstruktoren referenziert wird, dann ist isoliertes Testen unmöglich.

Rias A. Sherzad
Sep 2, 2006 13:04

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
Jun 18, 2007 11:54

Der Artikel ist wirklich gut geschrieben, hat nur einen kleinen Haken: das double checked locking idiom gilt als „gebrochen“.

Siehe:
http://en.wikipedia.org/wiki/Double-checked_locking

Rias A. Sherzad
Sep 24, 2007 23:50

Ja, das sagt der Artikel aber auch…

Singleton in Java — ingokallenbach.de
Aug 17, 2008 13:09

[…] Artikel Singleton Pattern in Java beschreibt einige Implementierungen des Singletons in Java, bevor am Ende auf die bisher oft […]

E.L.
Jan 12, 2009 22:43

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!“

Rias A. Sherzad
Jan 12, 2009 22:49

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.
Jan 12, 2009 22:52

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.
Jan 12, 2009 22:53

Gern geschehen :)

Rias A. Sherzad
Jan 12, 2009 22:57

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 :-)

Anonymous
Jul 4, 2009 18:59

[…] 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
Jan 29, 2010 22:32

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
Feb 2, 2010 16:37

[…] 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
Aug 10, 2010 16:36

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.

  1. /**
  2. * Implementierung des Singleton-Patterns mit synchronisiertem
  3. * Zugriff auf die Instanziierung und einfacher Bedingungsprüfung
  4. */
  5. public class VerySingleton {
  6.     private static VerySingleton instance = null;
  7.     private static Boolean created = false;
  8.     /**
  9.      * Default-Konstruktor, der nicht außerhalb dieser Klasse
  10.      * aufgerufen werden kann
  11.      */
  12.     private VerySingleton() {}
  13.  
  14.     /**
  15.      * Statische Methode, liefert die einzige Instanz dieser
  16.      * Klasse zurück
  17.      */
  18.     public static VerySingleton getInstance() {
  19.         if (!created) {
  20.             synchronized(VerySingleton.class) {
  21.                 if(!created)
  22.                     instance = new VerySingleton();
  23.                 created = true;
  24.             }
  25.         }
  26.         return instance;
  27.     }
  28. }

Kommentieren

Weitere Empfehlungen:




-->

Java - Jul 19, 2011 20:30 - 0 Kommentare

Eine (wirklich gute) Einführung in Maven

Mehr Artikel der Kategorie Softwarearchitekturen


Datenbanken, Internet Technologien - Nov 19, 2009 9:24 - 0 Kommentare

Oracle WebServices im praktischen Einsatz

More In Datenbanken


Betriebssysteme - Jul 27, 2006 22:28 - 0 Kommentare

Angriff der Klone!

More In Betriebssysteme