poniedziałek, 22 sierpnia 2011

Jak poprawnie zaimplementować wzorzec Singleton w Javie.

             Cel tego wzorca jest bardzo prosty tak jak on sam. Ma on zapewnić, że w naszej aplikacji istnieje tylko jedna instancja danej klasy. Jako, że mamy tylko jeden obiekt takowej klasy, trzeba oczywiście zapewnić do niego jakiś punkt dostępu, zwykle poprzez metodę statyczną. Pierwsza rzecz jaka przychodzi na myśl, to ograniczenie do konstruktora do własnej klasy i podklas.  Konstruktor musi być zatem opatrzony modyfikatorem private lub protected. Oczywiste też jest, że instancja musi być przechowywana w postaci prywatnego pola statycznego zainicjowanego wartością null. Najciekawiej prezentuje się metoda, która zarządza tym polem getInstance(). Jako, że stanowi ona nie jako punkt dostępu dla innych klas, musi być opatrzona modyfikatorem typu public.




Pojedyncza instrukcja if, która sprawdza czy instancja klasy nie została wcześniej utworzona mogła by nie wystarczyć. Wyobraźmy sobie taką sytuacje, że wątek który właśnie sprawdził czy instance == null zostaje wstrzymany na rzecz drugiego wątku.



Drugi wątek sprawdza warunek i tworzy obiekt. Może się zdarzyć, że wcześniejszy wątek znowu przejmie kontrolę w miejscu gdzie został wstrzymany czyli po wejściu do instrukcji if i ponownie utworzy nam instancje tej klasy. Może to wydawać się skomplikowane i zagmatwane ale kto nie wierzy ten niech sprawdzi. Można temu zapobiec stosują blok synchronizujący synchronized - zapewnia on nam, że kod w tym bloku w danym czasie może być wykonywany tylko przez jeden wątek.


Wydawać by się mogło, że jest to doskonałe rozwiązanie. Mamy tu jeden haczyk. Jaki? Jeśli klasa, ta implementowała by interfejs Serializable to po przez mechanizm serializacji można by utworzyć wiele takich obiektów. Spójrzmy na poniższy kod:



Rozwiązanie to wykorzystuje mechanizm class loader'ów. Jest on zapewniony przez maszynę wirtualną. Mechanizm ten jest synchronizowany. Obiekty class loader zapewniają ładowanie klas. Wewnątrz maszyny wirtualnej są zorganizowane w postaci drzewa. Jeśli taki obiekt otrzyma żądanie załadowania danej klasy to w celu uniknięcia wielokrotnego ładowania tej samej klasy, sprawdza czy jego nadrzędny class loader nie zrobił tego wcześniej.  Klasa wewnętrzna SingletonHolder posiada prywatne statyczne i niemodyfikowalne pole. Jak widzimy jest to instancja klasy Singleton. Kiedy zostaje wywołana metoda getInstance() class loader załaduje tylko jeden raz klasę SingletonHolder.

Istnieją jeszcze inne sposoby na poradzenie sobie z tym problemem. Przytoczyłem  dwa najbardziej popularne rozwiązania.

2 komentarze:

  1. Dla mnie jako początkującego adepta java, jest to bardzo fajnie opisane i wyjaśnione.

    OdpowiedzUsuń
  2. Ile lat temu to napisałem nie pamiętam. Istnieje jeszcze opcja przez enum co polecam...

    OdpowiedzUsuń

Labels