Singletony w Javie

1. Wstęp

W tym krótkim artykule omówimy dwa najpopularniejsze sposoby implementacji Singletonów w zwykłej Javie.

2. Singleton oparty na klasach

Najpopularniejszym podejściem jest implementacja Singletona poprzez utworzenie zwykłej klasy i upewnienie się, że ma:

  • Prywatny konstruktor
  • Pole statyczne zawierające jedyną instancję
  • Statyczna metoda fabryczna uzyskiwania instancji

Dodamy również właściwość info, tylko do późniejszego wykorzystania. Tak więc nasza realizacja będzie wyglądać następująco:

public final class ClassSingleton { private static ClassSingleton INSTANCE; private String info = "Initial info class"; private ClassSingleton() { } public static ClassSingleton getInstance() { if(INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; } // getters and setters }

Chociaż jest to powszechne podejście, należy zauważyć, że może to być problematyczne w scenariuszach wielowątkowych , co jest głównym powodem używania singletonów.

Mówiąc najprościej, może to spowodować więcej niż jeden przypadek, łamiąc podstawową zasadę wzoru. Chociaż istnieją blokujące rozwiązania tego problemu, nasze następne podejście rozwiązuje te problemy na poziomie głównym.

3. Enum Singleton

Idąc dalej, nie omawiajmy innego ciekawego podejścia - jakim jest wykorzystanie wyliczeń:

public enum EnumSingleton { INSTANCE("Initial class info"); private String info; private EnumSingleton(String info) { this.info = info; } public EnumSingleton getInstance() { return INSTANCE; } // getters and setters }

To podejście ma serializację i bezpieczeństwo wątków gwarantowane przez samą implementację wyliczenia, co zapewnia wewnętrznie, że dostępne jest tylko jedno wystąpienie, korygując problemy wskazane w implementacji opartej na klasach.

4. Użytkowanie

Aby skorzystać z naszego ClassSingleton , musimy po prostu pobrać instancję statycznie:

ClassSingleton classSingleton1 = ClassSingleton.getInstance(); System.out.println(classSingleton1.getInfo()); //Initial class info ClassSingleton classSingleton2 = ClassSingleton.getInstance(); classSingleton2.setInfo("New class info"); System.out.println(classSingleton1.getInfo()); //New class info System.out.println(classSingleton2.getInfo()); //New class info

Jeśli chodzi o EnumSingleton , możemy go używać jak każdego innego Java Enum:

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance(); System.out.println(enumSingleton1.getInfo()); //Initial enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance(); enumSingleton2.setInfo("New enum info"); System.out.println(enumSingleton1.getInfo()); // New enum info System.out.println(enumSingleton2.getInfo()); // New enum info

5. Typowe pułapki

Singleton jest zwodniczo prostym wzorcem projektowym i jest kilka typowych błędów, które programista może popełnić podczas tworzenia singletona.

Wyróżniamy dwa rodzaje problemów z singletonami:

  • egzystencjalny (czy potrzebujemy singletona?)
  • wdrożeniowe (czy właściwie to wdrażamy?)

5.1. Kwestie egzystencjalne

Koncepcyjnie singleton jest rodzajem zmiennej globalnej. Ogólnie wiemy, że należy unikać zmiennych globalnych - zwłaszcza jeśli ich stany są zmienne.

Nie mówimy, że nigdy nie powinniśmy używać singletonów. Jednak mówimy, że mogą istnieć bardziej wydajne sposoby organizacji naszego kodu.

Jeśli implementacja metody zależy od pojedynczego obiektu, dlaczego nie przekazać jej jako parametru? W tym przypadku wyraźnie pokazujemy, od czego zależy metoda. W konsekwencji możemy łatwo mockować te zależności (jeśli to konieczne) podczas przeprowadzania testów.

Na przykład singletony są często używane do uwzględnienia danych konfiguracyjnych aplikacji (tj. Połączenia z repozytorium). Jeśli są używane jako obiekty globalne, trudno jest wybrać konfigurację dla środowiska testowego.

Dlatego też, kiedy przeprowadzamy testy, produkcyjna baza danych zostaje zepsuta danymi testowymi, co jest trudne do zaakceptowania.

Jeśli potrzebujemy singletona, możemy rozważyć możliwość delegowania jego instancji do innej klasy - pewnego rodzaju fabryki - która powinna zapewnić, że w grze jest tylko jedna instancja singletona.

5.2. Problemy wdrożeniowe

Mimo że singletony wydają się dość proste, ich implementacje mogą mieć różne problemy. Wszystko to powoduje, że możemy mieć więcej niż jedną instancję tej klasy.

Synchronizacja

Implementacja z prywatnym konstruktorem, którą przedstawiliśmy powyżej, nie jest bezpieczna dla wątków: działa dobrze w środowisku jednowątkowym, ale w środowisku wielowątkowym powinniśmy użyć techniki synchronizacji, aby zagwarantować atomowość operacji:

public synchronized static ClassSingleton getInstance() { if (INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; }

Zwróć uwagę na słowo kluczowe zsynchronizowane w deklaracji metody. Treść metody ma kilka operacji (porównanie, tworzenie instancji i powrót).

W przypadku braku synchronizacji, istnieje możliwość, że dwa wątki przeplatają swoje egzekucje w taki sposób, że wyrażenie ZDARZEŃ == null ma wartość prawda dla obu nitek i, w rezultacie, dwie instancje ClassSingleton się stworzył.

Synchronizacja może znacząco wpłynąć na wydajność. Jeśli ten kod jest często wywoływany, powinniśmy go przyspieszyć za pomocą różnych technik, takich jak leniwa inicjalizacja lub podwójnie sprawdzane blokowanie (pamiętaj, że może to nie działać zgodnie z oczekiwaniami z powodu optymalizacji kompilatora). Więcej szczegółów można znaleźć w naszym samouczku „Podwójnie sprawdzane blokowanie za pomocą singletona”.

Wiele instancji

Istnieje kilka innych problemów z singletonami związanymi z samą maszyną JVM, które mogą spowodować, że skończymy z wieloma wystąpieniami singletona. Te kwestie są dość subtelne i podamy krótki opis każdego z nich:

  1. Singleton powinien być unikalny dla każdej maszyny JVM. Może to stanowić problem w przypadku systemów rozproszonych lub systemów, których elementy wewnętrzne są oparte na technologiach rozproszonych.
  2. Każdy program ładujący klasy może załadować swoją wersję singletona.
  3. Singleton może zostać zebrany jako śmieci, gdy nikt nie ma do niego odniesienia. Ten problem nie prowadzi do obecności wielu pojedynczych wystąpień jednocześnie, ale po odtworzeniu instancja może różnić się od poprzedniej wersji.

6. Wniosek

W tym krótkim samouczku skupiliśmy się na tym, jak zaimplementować wzorzec Singleton przy użyciu tylko podstawowej Javy oraz jak upewnić się, że jest spójny i jak korzystać z tych implementacji.

Pełną implementację tych przykładów można znaleźć na GitHub.