Meffert IT Consulting

JUnit Profi Tipps


JUnit Profi-Tipps kaufen

Das Entwurfsmuster Singleton testen

Der folgende Text ist ein Auszug aus dem Buch
JUnit Profi-Tipps - Software erfolgreich testen

Das Entwurfsmuster Singleton ist der populärste Vertreter der Erzeugungsmuster. Es wird immer dann eingesetzt, wenn von einem Objekt genau eine Instanz existieren soll. Eine Abwandlung ist das Fewton, das, wörtlich übersetzt, einige wenige Instanzen zulässt.

Eine typische Implementierung des Singleton ist im folgenden Listing abgebildet, Zeilennummern sind zur besseren Orientierung angegeben.

0 public class SingletonClass {
1   private static SingletonClass instance;
2   private SingletonClass() {...};
3   public static SingletonClass getInstance() {

4     if (instance == null) {
5       instance = new SingletonClass();
6     }
7     return instance;
8   }
9 }

Listing 1: Charakteristischer Teil eines Singletons

Die typischen Merkmale einer traditionellen Singleton-Klasse sind:

  • Die statische Methode getInstance() (Zeilen 3 bis 8 im obigen Listing) erzeugt einmalig die Singleton-Instanz und liefert sie zurück (ab dem zweiten Aufruf ohne das Objekt neu zu erzeugen).

  • Es existiert nur ein privater Konstruktor ohne Parameter (Zeile 2). So ist der Entwickler gezwungen, die Methode getInstance() zur Objekterzeugung zu verwenden.

  • Jeder Aufruf von getInstance() liefert dasselbe Objekt zurück (Zeilen 4 bis 7).

  • Die private statische Variable instance speichert die Singleton-Instanz (Zeile 1).

Anstatt also zu schreiben

SingletonClass myObj = new SingletonClass();

wird das Singleton durch folgende Anweisung angewandt:

SingletonClass myObj = SingletonClass.getInstance();

Weil die Methode getInstance() statisch ist, kann sie jederzeit aufgerufen werden. Das ist auch notwendig, weil ja gerade so eine (beim ersten Aufruf nicht vorhandene) Objektinstanz erzeugt werden soll und weil der Konstruktor privat ist.

Das Singleton testen

Ein Singleton-Test auf Anwendungsebene gestaltet sich recht einfach:

public void testSingleton() {
 
MyClass myObj1 = MyClass.getInstance();
 
MyClass myObj2 = MyClass.getInstance();
 
assertSame(myObj1, myObj2);
}

Listing 2: Test eines Singletons

Tools wie PMD prüfen im Ansatz, ob für eine Klasse das Singleton angebracht wäre. PMD etwa schlägt vor, aus einer (konkreten) Klasse ein Singleton zu machen, wenn diese nur statische Methoden enthält.

Thread-sichere Singleton-Version testen

Die wohl am weitesten verbreitete, einfachste Implementierung der Singleton-Methode getInstance() ist nicht Thread-sicher. Das bedeutet, es kann zu Problemen beim parallelen Zugriff zweier Objekte auf dasselbe Singleton kommen. Bezogen auf das Listing 1 ergibt sich folgende Problematik:

Thread 1 ruft zuerst getInstance() auf. Die Abarbeitung befinde sich nun in Zeile 5 (denn die Variable instance ist anfangs immer null), die aber noch nicht ausgeführt wurde. Thread 2 ruft ebenfalls die Methode auf. In Zeile 4 ist instance für Thread 2 noch mit null belegt. Daher wird auch für Thread 2 Zeile 5 ausgeführt. SingletonClass wird also zweimal instantiiert. Das widerspricht jedoch dem Charakter eines Singletons. Ein Singleton soll schließlich garantieren, dass immer höchstens eine Instanz der Singleton-Klasse existiert.

Das Thema Thread-Sicherheit kann beliebig komplex werden, es sei nur an die Möglichkeit erinnert, mit mehreren virtuellen Maschinen zu arbeiten statt mit nur einer. Daher soll hier nur eine Idee vermittelt werden. Der Abschnitt Threads testen im Kapitel 5 des Buchs enthält weitere Informationen zum Thema.

Der Test, ob ein Singleton Thread-sicher ist, ist nicht ganz einfach. Denn hierzu müssen mindestens zwei Threads ein Singleton aufrufen und der zweite Thread muss den ersten an einer ganz bestimmten Stelle überholen. Diese Stelle befindet sich nach Zeile 4 und vor Zeile 5 in Listing 1. Zunächst der Test einer Singleton-Implementierung hin auf Thread-Sicherheit:

import junit.framework.TestCase;

public class SingletonTest extends TestCase {

 
private static Singleton singleton = null;

 
public void testSingleton() throws Exception {
    
Thread thread1 = new Thread(new SingletonRun());
    Thread thread2 = new Thread(new SingletonRun());


   
thread1.start(); //Thread starten
    thread2.start();


   
thread1.join(); //Auf Ende des Threads warten
    thread2.join();

  
}

  
private class SingletonRun implements Runnable {
   
public void run() {
      
Singleton s = Singleton.getInstance();
     
synchronized(SingletonTest.class) {
        if(singleton == null)
          singleton = s;
      }

     
assertSame(s, singleton);
   
}
  
}
}

Listing 3: Ein Singleton auf Thread-Sicherheit testen

Der obige Test wird so gut wie immer erfolgreich durchlaufen. Um den Test scheitern zu sehen, ist die Implementierung von getInstance() in der Singleton-Klasse geschickt anzupassen: Anstatt der Anweisung

instance = new SingletonClass();

in Zeile 5 von Listing 1 kann etwa folgendes geschrieben werden:

instance = createSingletonClass();

und als Ergänzung der Klasse SingletonClass:

protected SingletonClass createSingletonClass() {
 
return new SingletonClass();
}

Nun bringt dies nicht viel, ausser SingletonClass wird für den Test abgeleitet und in der abgeleiteten Klasse wird vor der obigen return-Anweisung folgendes eingesetzt:

Thread.currentThread().sleep(50);

Jetzt schlägt der oben angegebene Test zur Thread-Sicherheit fehl!

Die Thread-sichere Variante des Singletons ist überraschend einfach zu implementieren, etwa so:

public class MySingleton {
   public final static Singleton INSTANCE = new MySingleton();
   private MySingleton() {
       }
}

Listing 4: Thread-sichere Singleton-Variante

Die Variante umgeht die Schwierigkeiten, die mit dem Double-Checked Locking, dem synchronization-Keyword und anderen Spielarten verbunden sind. Anstatt die bekannte Methode getInstance() zu verwenden, greifen Klienten auf die vorinitialisierte statische Variable namens INSTANCE zu. Der Nachteil dieser einfachen Lösung ist deren Inflexibilität. Im Gegensatz zu komplexeren Lösungen, die eher anfällig gegen Zugriffskonflikte sind, kann diese Variante nicht an andere Bedürfnisse angepasst werden.

Eine gute Ausgangsbasis für die Realisierung komplexerer, Thread-sicherer Singleton-Varianten sind die Queue-Klassen im Package java.util.concurrent (Java 5) bzw. edu.emory.mathcs.backport.java.util.concurrent (Java 1.4 Backport).