Singleton jest to wzorzec projektowy, który pozwala na stworzenie tylko jednej instancji danej klasy w aplikacji oraz zapewnia do niej globalny dostęp. Stosowany jest gdy, chcemy mieć pewność ze dany obiekt zostanie stworzony tylko raz w naszej aplikacji.  Proponowane podejście ma szczególne znaczenie gdy w naszej aplikacji może istnieć tylko jeden ogólnodostępny obiekt danej klasy np. klasy zawierające globalne configi lub połączenia z bazą danych. Poniżej przykłady implementacji:

1. Implementacja z „eager” inicjalizacją

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    
    private EagerSingleton() {
        // prywatny konstruktor
    }
    
    public static EagerSingleton getInstance() {
        return instance;
    }
}

Instancja singletona zostanie utworzona w momencie załadowania klasy przez wirtualną maszynę Javy.
W związku z tym nigdy nie występuje przypadek, gdy nie ma instancji danej klasy.

2. Implementacja z „lazy” inicjalizacją

public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {
        // prywatny konstruktor
    }
    
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

W przypadku leniwej inicjalizacji singletona, instancja klasy zostaje utworzona dopiero w momencie pierwszego wywołania metody getInstance(). Synchronizacja zapewnia bezpieczeństwo w środowisku wielowątkowym.

3. Implementacja z „double-check”

public class ThreadSafeSingleton {
    private static volatile ThreadSafeSingleton instance;
    
    private ThreadSafeSingleton() {
        // prywatny konstruktor
    }
    
    public static ThreadSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (ThreadSafeSingleton.class) {
                if (instance == null) {
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }
}

W przypadku implementacji z double-check, singleton jest bezpieczny w środowisku wielowątkowym. Zmienna volatile zapewnia, że każdy wątek widzi najnowszą wartość singletona. Natomiast blok synchroniczny ponownie weryfikuje istnienie instancji klasy. Dzięki temu rozwiązaniu wątki są zakolejkowane i nie powstaje duplikat instancji singletona.

4. Implementacja jako Enum

public enum EnumSingleton {
    INSTANCE;
}

Implementacja singletona jako enum jest krótka i bezpieczna w środowisku wielowątkowym. Enum w Javie jest tworzony tylko raz w czasie działania aplikacji, co zapewnia, że Singleton jest dostępny globalnie. Ponadto metody instancji mogą być dodane wewnątrz enuma.

5. Implementacja z zabezpieczeniem przed serializacją

public class SerializableSingleton implements Serializable {
    private static Singleton instance = new Singleton();

    private SerializableSingleton () {
        // prywatny konstruktor
    }

    public static SerializableSingleton getInstance() {
        return instance;
    }

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

W powyższej implementacji, klasa SerializableSingleton implementuje interfejs Serializable, który jest wymagany do serializacji i deserializacji obiektów w Javie. Metoda readResolve() zapewnia, że deserializowana instancja singletona jest zawsze taka sama jak ta, która została utworzona podczas pierwszego wywołania metody getInstance(). W ten sposób, unikamy problemów związanych z tworzeniem wielu instancji singletona.

Istnieją różne sposoby implementacji wzorca Singleton. Jednak wybór odpowiedniej implementacji zależy ściśle od wymagań projektowych.