Singleton

Singleton jest wzorcem stosowanym w przypadku gdy chcemy zapewnić że w obrębie jednej maszyny wirtualnej javy będzie tylko jedna instancja danej klasy.

 

Schemat

 

Założenia

  1. Klasa która ma być singletonem musi posiadać prywatne, statyczne pole instance typu naszego singletona
  2. Musi posiadać publiczną, statyczną metodę getInstance zwracającą obiekt singletona
  3. Musi posiadać prywatny konstruktor

Schemat działania

  1. Do pola typu SingletonObject przypisujemy obiekt zwrócony za pomocą metody getInstance
  2. Tak jak w punkcie 1 z tym że przypisujemy obiekt do innej zmiennej (singleton2)
  3. Porównujemy wartość referencji (zwraca true ponieważ w rzeczywistości dwie różne zmienne wskazują na dokładnie ten sam obiekt w pamięci)
public class SingletonObject {

    private static SingletonObject instance;

    private SingletonObject() {
    }

    public static SingletonObject getInstance() {
        if(instance == null) {
            instance = new SingletonObject();
        }
        return instance;
    }
}
public class Main {
    public static void main(String args[]) {

        SingletonObject singleton = SingletonObject.getInstance();
        SingletonObject singleton2 = SingletonObject.getInstance();

        System.out.println(singleton==singleton2);
    }
}

 

Potencjalne problemy

Singleton może być potencjalnie problematyczny w kilku kwestiach:

1) Wielowątkowość

Pierwszy wątek sprawdza instance który okazuje się nullem (petla if) ale zanim zdąży utworzyć nowy obiekt, drugi wątek również sprawdza czy instance jest nullem (i będzie bo pierwszy wątek jeszcze nie zdążył utworzyć obiektu). W efekcie utworzone zostałyby dwie instancje klasy która miała być singletonem.

Sposoby by temu zapobiec:
a) Zrobienie metody getInstance całościowo synchroniczną
public synchronized static SingletonObject getinstance();

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

Wadą tego rozwiązania jest to że niezależnie czy będziemy mieli już instancję obiektu czy jeszcze nie, w tej metodzie będziemy przywoływać jeden wątek, wszystkie inne będą czekać.

b) Podwójny check
1) Sprawdzamy czy instance jest nullem za pomocą warunku if
2) Potem blok synchroniczny:
synchronized (SingletonObject.class)
3) Znowu sprawdzamy za pomocą warunku if czy jest nullem (czy się w tym czasie nie utworzył obiekt)
4) Dopiero tworzenie obiektu

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

c)
Gdy mamy statyczne pole instance mozemy od razu utworzyc nowy obiekt

private static SingletonObject instance = SingletonObject();

Dzięki temu w metodzie getInstance można pominąć sprawdzanie czy instance jest nullem i od razu zwrócić instance ponieważ obiekt tworzony jest od razu po załadowaniu klasy do classloadera.

public static SingletonObject getInstance() {
    return instance;
}

2) Classloader

Singleton zapewnia unikalność tylko na poziomie jednego classloadera (sprawia to problem jeżeli stosuje się wiecej classloaderów dlatego powinno się tego unikać).

3) Serializacja

Singletona można zserializować a potem odserializować jego dowolną ilość kopii.
Aby tego uniknąć wystarczy zaimplementować metodę

protected Object readResolve() {
     return getInstance(); 
}

Ta metoda wywoła się za każdym razem gdy obiekt klasy singletona zostanie odserializowany – wystarczy zwrócić już istniejący obiekt instance.

4) Refleksje

Klasę która ma być singletonem można zaimplementować jako enum.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *