IT-Academy Logo
Sign Up Login Help
Home - Programmieren - Java - Entwurfsmuster: Singleton



Entwurfsmuster: Singleton

Das Singleton-Entwurfsmuster dient zur Erstellung eines einzigartigen Objekts. Wie dies in Java implementiert wird und welche Probleme man unbedingt beachten sollte, wird in diesem Artikel beschrieben.


Autor: Patrick Bucher (paedubucher)
Datum: 16-10-2006, 09:18:20
Referenzen: siehe 'Referenzen' am Ende des Artikels
Schwierigkeit: Fortgeschrittene
Ansichten: 8128x
Rating: 9 (1x bewertet)

Hinweis:

Für den hier dargestellte Inhalt ist nicht der Betreiber der Plattform, sondern der jeweilige Autor verantwortlich.
Falls Sie Missbrauch vermuten, bitten wir Sie, uns unter missbrauch@it-academy.cc zu kontaktieren.

[Druckansicht] [Als E-Mail senden] [Kommentar verfassen]



Das Singleton-Entwurfsmuster

Das Singleton-Entwurfsmuster gilt im Bezug auf die Komplexität als das einfachste Entwurfsmuster, da es nur aus einer einzigen Klasse besteht. Eine Klasse wird immer dann als Singleton implementiert, wenn es nur eine einzige Instanz von ihr geben darf. Singleton garantiert, dass es von einer Klasse höchstens eine Instanz geben kann und bietet dazu einen globalen Zugriffspunkt auf diese Instanz. Dies kann bei verschiedenen Verwendungszwecken sehr hilfreich sein:

  • Datenbankverbindungen
  • Dialoge
  • Logging-Objekte
  • Objekte, die globale Daten und Einstellungen verwalten

Bei all diesen Anwendungsbeispielen geht es darum, dass die Objekte nur eine einzige Instanz haben dürfen, um so Inkonsistenzen zu vermeiden.

Aufbau

Wie bereits erwähnt, besteht das Singleton-Entwurfsmuster nur aus einer einzigen Klasse. Diese verfügt im Wesentlichen über die folgenden Merkmale:

  1. Eine statische Variable des gleichen Typs
  2. Einen privaten Konstruktor
  3. Einer statischen Methode, welche die Instanz zurück gibt

Oder als UML2-Klassendiagramm ausgedrückt:


Definition

Das Singleton-Muster sichert, dass es nur eine Instanz einer Klasse gibt und bietet einen globalen Zugriffspunkt für diese Instanz.

Implementierung

(01)
(02)
(03)
(04)
(05)
(06)
(07)
(08)
(09)
(10)
(11)
(12)
(13)
(14)
(15)
(16)
(17)
package singleton;

public class Singleton
{
  private static Singleton instance = null;

  private Singleton() {}
       
  public static Singleton getInstance()
  {
    if (Singleton.instance == null)
    {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

Die Implementierung kann eins zu eins vom Klassendiagramm übernommen werden. Das einzige Interessante ist die Methode getInstance() ab Zeile (09). Zunächst wird geprüft, ob die statische Eigenschaft instance bereits instantiiert wurde, sodass sie nicht mehr auf null verweist. Falls instance immer noch auf null verweist, so wird eine neue Instanz unserer Singleton-Klasse erstellt. Dies bezeichnet man auch als lazy instantiation Die Rückgabe der Instanz erfolgt in jedem Fall.

Der ganze Trick am Singleton-Entwurfsmuster besteht also darin, dass die Klasse ihre Instanzen selbst verwaltet. Der private Konstruktor verhindert, dass eine Instanz der Singleton-Klasse aus einer anderen Klasse heraus erstellt werden kann.

Probleme beim Multithreading

Die beschriebene Implementierung ist grundsätzlich richtig. Dies gilt aber nur bei der Verwendung eines einzigen Threads. Verwendet man die Singleton-Klasse (genauer die Methode getInstance()) aus mehreren Threads heraus, so kann die Einzigartigkeit des Singleton-Objekts nicht länger gewährleistet werden!

Die Ursache dieses Problems ist einfach. Betrachten wir folgendes Szenario:

  • instance verweist am Anfang auf null
  • Thread 1 betritt die Methode getInstance()
    • Auf Zeile (11) wird geprüft, ob die statische Instanz bereits auf ein existierendes Objekt verweist
    • Da instance immer noch auf null verweist, wird der bedingte Block betreten
  • Thread 2 kommt an die Reihe und betritt nun ebenfalls die Methode getInstance()
    • Da Thread 1 instance noch nicht instanziieren konnte, verweist es immer noch auf null
    • Auch in Thread 2 wird der bedingte Block betreten
  • Thread 1 ist wieder an die Reihe und instantiiert die Singleton-Klasse (Zeile (13))
  • Thread 2 erhält Rechenzeit und erstellt ebenfalls eine Instanz der Singleton-Klasse

Wir verfügen also nun über zwei Instanzen unserer Singleton-Klasse!

Lösungsvorschlag 1

Die erste Möglichkeit, unser Synchronisationsproblem zu lösen besteht darin, dass die Methode getInstance() einfach mit dem Schlüsselwort synchronized definiert wird:

(01)
(02)
(03)
(04)
(05)
(06)
(07)
(08)
(09)
(10)
(11)
(12)
(13)
(14)
(15)
(16)
(17)
package singleton;

public class Singleton
{
  private static Singleton instance = null;

  private Singleton() {}

  public static synchronized Singleton getInstance()
  {
    if (Singleton.instance == null)
    {
      Singleton.instance = new Singleton(); 
    }
    return Singleton.instance;
  }
}

Nun kann immer nur ein Thread gleichzeitig unsere Methode abarbeiten. Das Problem ist also soweit gelöst. Das gleichzeitige Ausführen der Methode getInstance() von mehreren Threads aus ist jedoch nur beim ersten Aufruf der Methode problematisch! Beim zweiten Aufruf existiert die Instanz garantiert. Synchronisationsbedarf besteht also nur beim allerersten Durchlauf der Methode. Das Multi-Threading wird so nur unnötig eingeschränkt!

Die Methode getInstance() ist offensichtlich sehr klein. Für die meisten Anwendungen ist die Performance-Einbusse durch die synchronisierte Methode verschwindend gering. Eine Lösung mit synchronized ist also durchaus vertretbar.

Lösungsvorschlag 2

Statt der bisher verwendeten lazy instantiation können wir unsere Instanz auch von Anfang an erstellen:

(01)
(02)
(03)
(04)
(05)
(06)
(07)
(08)
(09)
(10)
(11)
(12)
(13)
package singleton;

public class Singleton
{
  private static Singleton instance = new Singleton();

  private Singleton() {}

  public static Singleton getInstance()
  {
    return instance; 
  }
}

Statt also instance am Anfang nur mit null zu initialisieren, erstellen wir nun gleich eine Instanz unserer Singleton-Klasse. Die Java-Laufzeitumgebung garantiert uns also, dass wir immer eine Instanz haben - garantiert Thread-sicher! Das Problem an dieser Lösung besteht jedoch darin, dass wir auch eine Instanz erstellen, wenn diese in der Applikation möglicherweise gar nicht verwendet wird! Wir verschenken also unnötig Speicher und nehmen eine etwas längere Startzeit unserer Anwendung in Kauf.

Lösungsvorschlag 3

Die dritte Lösungsansatzes stellt eigentlich nur eine Verfeinerung des ersten Lösungsansatzes dar. Die Methode getInstance() wird nicht in jedem Fall gesperrt, sondern nur beim ersten Durchlauf.

(01)
(02)
(03)
(04)
(05)
(06)
(07)
(08)
(09)
(10)
(11)
(12)
(13)
(14)
(15)
(16)
(17)
(18)
(19)
(20)
(21)
(22)
(23)
package singleton;

public class Singleton
{
  private volatile static Singleton instance = null;

  private Singleton() {}

  public static Singleton getInstance()
  {
    if (Singleton.instance == null)
    {
      synchronized (Singleton.class)
      {
        if (Singleton.instance == null)
        {
          Singleton.instance = new Singleton(); 
        }
      }
    }
    return instance;
  }
}

Auf Zeile (13) wird ein synchronisierter Codeblock eingeleitet. Statt die Methode nur für alle anderen Threads zu sperren, wird nur ein einzelner Befehlsblock innerhalb der Methode synchronisiert. Und diese Synchronisierung ist auch nur dann notwendig, wenn unsere Instanz noch nicht erstellt wurde. Dies wird auf Zeile (11) geprüft.

Befinden wir uns erst einmal im synchronisierten Block, so müssen wir auf Zeile (15) diese Prüfung noch einmal durchführen, um sicherzustellen, dass vor dem Sperren des Objekts (mithilfe des synchronized-Schlüsselwortes) noch keine Instanz erzeugt wurde. Erst dann wird auf Zeile (17) unsere Instanz erzeugt.

Auf Zeile (5) bei der Deklaration von instance ist ein neues Schlüsselwort hinzu gekommen; volatile. Dieses Schlüsselwort sichert, dass mehrere Threads richtig mit der Eigenschaft umgehen, wenn sie dann auf Zeile (17) instantiiert wird.

Achtung:
Das zweifach-geprüfte Sperren funktioniert erst ab JDK 1.5! Wer die JDK-Version 1.4 oder eine noch ältere Version einsetzt, der muss auf eine andere Variante zurückgreifen (Lösungsvorschlag 1 oder 2).

Referenzen

  • Entwurfsmuster von Kopf bis Fuss
    • Eric und Elisabeth Freeman
    • O'Reilly-Verlag
    • ISBN: 3-89721421-0

© 2006, Patrick Bucher



[back to top]



Userdaten
User nicht eingeloggt

Gesamtranking
Werbung
Datenbankstand
Autoren:04503
Artikel:00815
Glossar:04116
News:13565
Userbeiträge:16551
Queueeinträge:06238
News Umfrage
Ihre Anforderungen an ein Online-Zeiterfassungs-Produkt?
Mobile Nutzung möglich (Ipone, Android)
Externe API Schnittstelle/Plugins dritter
Zeiterfassung meiner Mitarbeiter
Exportieren in CSV/XLS
Siehe Kommentar



[Results] | [Archiv] Votes: 1137
Comments: 0