Klasyczny Singleton gwarantuje stworzenie tylko jednej instancji obiektu. W Javie istnieje kilka sposobów implementacji, ale z uwzględnieniem wielowątkowości i serializacji należy wziąć pod uwagę niuanse:
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
enum EnumSingleton { INSTANCE; // metody }
private Object readResolve() { return getInstance(); }
Czy wystarczy używać metody synchronized getInstance() dla thread-safe Singleton?
Odpowiedź: Tak, ale takie podejście prowadzi do spadku wydajności, ponieważ synchronized jest wywoływana przy każdym odwołaniu do getInstance. Bardziej efektywne są Double-checked Locking + volatile dla instancji, lub użycie Enum do implementacji Singletona.
Przykład nieefektywnego kodu:
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
Historia
W systemie finansowym podczas implementacji singltona zapomniano dodać
volatiledo referencji instance w double-checked locking. W efekcie przy wysokich obciążeniach występowały losowe przypadki tworzenia dwóch instancji klasy, co prowadziło do inkonsystencji raportów.
Historia
W bibliotece logowania zrealizowano Singleton poprzez prywatny statyczny obiekt, jednak przy serializacji i późniejszej deserializacji (np. w środowisku klastrowym) powstawało kilka instancji. Problem rozwiązano przez dodanie readResolve().
Historia
W systemie analitycznym programista zaimplementował thread-safe Singleton poprzez synchronized metodę getInstance(). W systemie o dużym obciążeniu w momencie szczytu nastąpił nagły spadek wydajności, okazało się, że wywołania getInstance() (tysiące razy na sekundę) blokowały się nawzajem z powodu niepotrzebnej synchronizacji, chociaż inicjalizacja potrzebna była tylko raz.