Singleton w Javie - jak go poprawnie używać (i kiedy nie)

Menu kontekstowe w IntelliJ IDEA, opcja "Singleton" dla tworzenia klasy wzorca singleton java.

Napisano przez

Tymoteusz Sobczak

Opublikowano

26 mar 2026

Spis treści

Wzorzec singleton java rozwiązuje bardzo konkretny problem: w całej aplikacji ma istnieć tylko jedna instancja klasy, a dostęp do niej ma być przewidywalny i kontrolowany. W praktyce przydaje się przy współdzielonej konfiguracji, rejestrach, cache albo elementach, które nie powinny być tworzone wielokrotnie. W tym artykule pokazuję, jak go napisać poprawnie, gdzie najczęściej się psuje i kiedy lepiej w ogóle z niego zrezygnować.

Najpierw sprawdź, kiedy jedna instancja naprawdę pomaga

  • Singleton ogranicza klasę do jednej instancji i jednego publicznego punktu dostępu.
  • Najbezpieczniejszym praktycznie wariantem bywa enum, a najczytelniejszym klasyczny holder.
  • Ręczna implementacja wymaga uwagi przy wątkach, serializacji i refleksji.
  • W aplikacjach webowych liczy się też class loader i zakres kontenera, więc „jedna instancja” nie zawsze znaczy to samo.
  • Jeśli obiekt zaczyna przechowywać stan użytkownika, singleton zwykle staje się problemem, nie pomocą.

Czym singleton jest i kiedy naprawdę ma sens

Ja traktuję ten wzorzec jako narzędzie do kontrolowania dostępu do wspólnego zasobu, a nie jako wygodne miejsce na wrzucenie dowolnego stanu globalnego. Dobrze sprawdza się tam, gdzie jedna instancja ma sens biznesowy albo techniczny: centralna konfiguracja, rejestr, licznik, kolejka zdarzeń czy cienka warstwa dostępu do współdzielonego zasobu. Gorzej, gdy obiekt zaczyna udawać magazyn wszystkiego albo staje się ukrytym globalem.

Najważniejsza różnica jest prosta: singleton nie mówi „ta klasa jest statyczna”, tylko „tej klasy nie tworzymy wielokrotnie”. Dzięki temu można zachować metody instancyjne, hermetyzację i możliwość późniejszej podmiany implementacji. To właśnie dlatego ten wzorzec nadal ma sens w architekturze, mimo że często bywa nadużywany.

  • Ma sens, gdy jeden obiekt ma spinać współdzielony stan lub dostęp do zasobu.
  • Ma sens, gdy koszt tworzenia obiektu jest wysoki, a jego stan jest wspólny dla całej aplikacji.
  • Nie ma sensu, gdy potrzebujesz wielu niezależnych egzemplarzy tej samej klasy.
  • Nie ma sensu, gdy obiekt zaczyna przechowywać dane konkretnego użytkownika albo żądania.

Gdy już wiesz, po co ten wzorzec istnieje, warto zobaczyć, jak napisać go tak, żeby nie rozsypał się przy pierwszym równoległym wywołaniu.

Diagram ilustrujący wzorzec projektowy singleton w Javie. Trzech klientów (Client-1, Client-2, Client-3) żąda tej samej instancji (instance-1) z Singleton Class.

Jak napisać bezpieczny singleton w Javie

Najprostszy szkielet zawsze wygląda podobnie: prywatny konstruktor, prywatne pole z instancją i publiczna metoda zwracająca ten sam obiekt. W praktyce różni się głównie moment tworzenia instancji i sposób zabezpieczenia przed wyścigami między wątkami.

Wersja z natychmiastową inicjalizacją

To najprostszy wariant, który tworzę wtedy, gdy obiekt jest lekki i na pewno będzie używany. Zaletą jest banalna czytelność, wadą brak leniwej inicjalizacji.

public final class ConfigManager {
    private static final ConfigManager INSTANCE = new ConfigManager();

    private ConfigManager() {
    }

    public static ConfigManager getInstance() {
        return INSTANCE;
    }
}

Ten wariant działa dobrze, jeśli koszt utworzenia obiektu jest mały i nie przeszkadza ci, że powstanie od razu przy załadowaniu klasy. Ja wybieram go raczej do prostych komponentów niż do ciężkich usług.

Wersja z holderem

To zwykle mój domyślny wybór, gdy inicjalizacja może być droższa albo nie zawsze jest potrzebna. Klasa wewnętrzna ładuje się dopiero przy pierwszym wywołaniu getInstance(), a sam mechanizm opiera się na bezpiecznej inicjalizacji klas przez JVM.

public final class AppSettings {
    private AppSettings() {
    }

    private static class Holder {
        private static final AppSettings INSTANCE = new AppSettings();
    }

    public static AppSettings getInstance() {
        return Holder.INSTANCE;
    }
}

To rozwiązanie łączy leniwość z prostotą. Nie potrzebujesz blokad w metodzie, nie rozbudowujesz kodu bardziej niż to konieczne, a jednocześnie nie zostawiasz furtki na podwójne utworzenie obiektu.

Wersja z enumem

Jeśli nie potrzebujesz dziedziczenia po innej klasie, enum jest najczystszy. Daje odporność na serializację i bardzo mocną ochronę przed obejściem konstrukcji, dlatego w praktyce bywa najpewniejszym wyborem.

public enum CacheRegistry {
    INSTANCE;

    public void register(String key, Object value) {
        // logika rejestracji
    }
}

To nie jest tylko skrót składniowy. Dla wielu zespołów to po prostu najzdrowsza forma singletona, bo eliminuje kilka klas problemów, zanim w ogóle się pojawią. Gdy implementacja jest poprawna, kolejne pytanie brzmi już nie „jak to napisać”, tylko „co może mimo wszystko pójść nie tak”.

Co najczęściej psuje pojedynczą instancję

Wzorzec wygląda niewinnie, ale w realnym kodzie psują go zwykle trzy rzeczy: współbieżność, serializacja i próby obejścia mechanizmu przez refleksję. Do tego dochodzi jeszcze kontekst uruchomieniowy, który w aplikacjach serwerowych często bywa źle rozumiany.

Wielowątkowość

Najczęstszy błąd to leniwe tworzenie instancji bez synchronizacji. Dwie nitki mogą wtedy wejść do getInstance() jednocześnie i utworzyć dwa obiekty albo zobaczyć niedokończoną inicjalizację. Jeśli używasz podwójnego sprawdzania, pole z instancją musi być volatile, ale ja i tak częściej wybieram holder, bo jest czytelniejszy i mniej podatny na błędy.

Jeżeli singleton zarządza współdzielonym stanem, sam fakt posiadania jednej instancji nie wystarcza. Nadal trzeba zadbać o synchronizację wewnątrz metod albo o strukturę danych odporną na równoległy dostęp. Jedna instancja bez ochrony stanu to po prostu jeden wspólny problem dla wszystkich wątków.

Serializacja

Jeśli klasa implementuje serializację, zwykłe odtworzenie obiektu może stworzyć nową instancję zamiast zwrócić tę jedyną. Klasyczny sposób obrony to metoda readResolve(), która po deserializacji oddaje właściwy egzemplarz.

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

W przypadku enumów ten problem praktycznie znika, bo mechanizm języka traktuje je specjalnie. To jeden z powodów, dla których taki wariant tak często wygrywa z ręcznymi implementacjami.

Refleksja

private konstruktor nie jest magiczną tarczą. Refleksja potrafi go wywołać, jeśli ktoś świadomie obejdzie ograniczenia dostępu. Dlatego przy ważnych komponentach nie zakładam, że sam modyfikator dostępu wystarczy jako zabezpieczenie.

Jeśli naprawdę zależy ci na twardej gwarancji jednej instancji, enum daje mocniejszą ochronę niż zwykła klasa. W manualnym singletonie można dodać dodatkowe kontrole, ale to już komplikuje projekt i zwykle oznacza, że obiekt robi się bardziej wrażliwy, niż powinien.

Przeczytaj również: SOLID OOP w praktyce - Jak uporządkować kod obiektowy?

Class loader w aplikacji serwerowej

W środowisku webowym nie zakładaj automatycznie, że jedna instancja oznacza „jedną w całym JVM”. Ten sam kod załadowany przez różne class loadery może dostać osobne egzemplarze. To szczególnie ważne przy redeployu aplikacji i w systemach z wieloma modułami.

To prowadzi do pytania, które w projektach webowych ma duże znaczenie: gdzie kończy się singleton klasy, a zaczyna singleton kontenera. Właśnie tam najłatwiej o błędne założenia.

Singleton w aplikacji webowej i w Springu

W dokumentacji Springa zakres singleton oznacza jedną instancję w obrębie kontenera, a niekoniecznie jeden obiekt w całej aplikacji uruchomionej na wszystkich węzłach. To praktycznie wygodne, bo kontener sam pilnuje tworzenia i wstrzykiwania zależności, ale trzeba rozumieć granicę tego mechanizmu.

W aplikacji webowej taki obiekt zwykle żyje w granicach jednego kontekstu i jednego class loadera. Jeśli masz klaster, każdy węzeł ma własną kopię. Jeśli robisz redeploy, stara instancja znika razem z kontekstem, a na jej miejsce pojawia się nowa. To nie jest błąd, tylko naturalny skutek architektury serwera.

Dlatego w kodzie Springa często wolę zwykły bean w domyślnym zakresie niż ręcznie napisane getInstance(). Zyskuję testowalność, jawne zależności i prostsze podmienianie implementacji. Klasyczny singleton nadal bywa przydatny, ale częściej jako narzędzie niszowe niż domyślna odpowiedź.

Jeśli taki obiekt przechowuje cache, licznik albo połączenia, pamiętaj o współbieżności i o tym, że stan współdzielony musi być jawnie chroniony. To, że obiekt jest jeden, nie znaczy jeszcze, że jest bezpieczny. Skoro różne odmiany różnią się kosztami i odpornością, sensownie jest zestawić je obok siebie i wybrać bez zgadywania.

Którą wersję wybrać w praktyce

Jeśli mam wybierać bez długich dyskusji, patrzę na trzy rzeczy: koszt inicjalizacji, potrzebę leniwości i odporność na obejścia. Poniżej porównuję warianty, które realnie pojawiają się w projektach.

Wariant Zalety Ograniczenia Kiedy wybrać
Natychmiastowa inicjalizacja Najprostsza, czytelna, bez blokad Tworzy obiekt od razu Lekki obiekt, zawsze potrzebny
Holder Leniwa, bezpieczna, nadal prosta Wymaga zrozumienia klasy wewnętrznej Domyślny wybór w kodzie aplikacyjnym
Synchronizowana metoda Łatwo ją napisać i zrozumieć Blokada przy każdym wywołaniu Mały projekt, niski ruch, prostota ważniejsza niż wydajność
Podwójne sprawdzanie Wydajne po inicjalizacji Łatwo popełnić błąd, trudniejsze w utrzymaniu Gdy świadomie akceptujesz większą złożoność
Enum Najlepsza ochrona przed serializacją i refleksją Brak dziedziczenia po klasie Gdy singleton ma być prosty i naprawdę jeden

Moja praktyczna reguła jest prosta: jeśli obiekt ma być zwykłym, pewnym i odpornym singletonem, zaczynam od enum. Jeśli potrzebuję klasycznej formy i leniwej inicjalizacji bez skomplikowania kodu, wybieram holder. Pozostałe odmiany traktuję jako kompromis, a nie pierwszy wybór.

Kiedy lepiej zrezygnować z singletona

Najczęściej rezygnuję z tego wzorca wtedy, gdy widzę, że obiekt zaczyna robić za ukryty kontener zależności. Jeśli potrzebuję testować różne konfiguracje, uruchamiać równolegle kilka środowisk albo izolować stan per użytkownik, singleton tylko utrudni życie.

  • Stan zależy od konkretnego żądania, użytkownika albo sesji.
  • Musisz podmieniać implementację w testach lub w zależności od środowiska.
  • Potrzebujesz kilku instancji o różnych parametrach.
  • Obiekt zaczyna rosnąć do roli „miejsca na wszystko”.
  • Chcesz współdzielić stan między wieloma serwerami, a nie tylko w jednym procesie.

W takich sytuacjach lepiej sprawdzają się wstrzykiwanie zależności, fabryka, zwykły serwis bez stanu albo zewnętrzny magazyn danych. Ja patrzę na singleton jak na kompromis architektoniczny: działa dobrze, dopóki problem jest naprawdę pojedynczy. Gdy problem robi się wielokontekstowy, ten wzorzec przestaje pasować.

Zanim wdrożysz pojedynczą instancję, sprawdź te trzy rzeczy

Zanim uznam singleton za dobry wybór, sprawdzam trzy pytania. Czy obiekt naprawdę ma być wspólny dla całej aplikacji? Czy jego stan da się bezpiecznie chronić w wielu wątkach? Czy prostsze wstrzyknięcie zależności nie rozwiąże problemu czyściej niż ręczne getInstance()?

  • Wybierz enum, jeśli chcesz najtwardszą gwarancję jednej instancji i nie potrzebujesz dziedziczenia po klasie.
  • Wybierz holder, jeśli zależy ci na leniwej inicjalizacji i klasycznej formie klasy.
  • Zrezygnuj z singletona, jeśli obiekt zaczyna przechowywać stan użytkownika, sesji albo żądania.

Najlepszy singleton to taki, którego kod jest krótki, przewidywalny i łatwy do obrony architektonicznie. Gdy widzę, że trzeba go tłumaczyć przez trzy kolejne wyjątki i dwa hacki ochronne, zwykle oznacza to, że problem został źle nazwany. W takim momencie wolę prostszy serwis, jawne zależności i mniej ukrytego stanu.

FAQ - Najczęstsze pytania

Wzorzec singleton zapewnia, że dana klasa ma tylko jedną instancję w całej aplikacji, a dostęp do niej jest globalny i kontrolowany. Przydaje się do zarządzania zasobami, konfiguracją czy logowaniem, gdzie unikalność instancji jest kluczowa.

Singleton ma sens, gdy potrzebujesz jednej, wspólnej instancji obiektu do zarządzania zasobem (np. konfiguracja, cache, licznik) lub gdy koszt tworzenia obiektu jest wysoki, a jego stan jest wspólny dla całej aplikacji. Pamiętaj, by unikać go dla danych specyficznych dla użytkownika.

Najbezpieczniejsze i najczęściej rekomendowane implementacje to wzorzec z holderem (dla leniwej inicjalizacji) oraz enum (dla najwyższej odporności na serializację i refleksję). Unikaj ręcznej synchronizacji, która często prowadzi do błędów.

Należy unikać leniwej inicjalizacji bez odpowiedniej synchronizacji (ryzyko wielu instancji), zapominania o serializacji (może stworzyć nowe instancje) oraz nadmiernego polegania na modyfikatorach dostępu (refleksja może je obejść). Uważaj też na kontekst class loadera w aplikacjach webowych.

Zrezygnuj z singletona, gdy obiekt przechowuje stan specyficzny dla użytkownika, sesji czy żądania, gdy potrzebujesz wielu instancji o różnych parametrach, lub gdy chcesz łatwo podmieniać implementacje w testach. W takich przypadkach lepsze są wstrzykiwanie zależności czy fabryki.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi:

singleton java wzorzec singleton java singleton java implementacja singleton w javie kiedy używać singleton java wady i zalety bezpieczny singleton java

Udostępnij artykuł

Tymoteusz Sobczak

Tymoteusz Sobczak

Nazywam się Tymoteusz Sobczak i mam 9-letnie doświadczenie w programowaniu webowym. Moja przygoda z tą dziedziną zaczęła się od fascynacji tworzeniem stron internetowych, co z czasem przerodziło się w pasję do dzielenia się wiedzą i pomagania innym w odkrywaniu tajników programowania. Lubię wyjaśniać złożone zagadnienia w przystępny sposób, co pozwala moim czytelnikom lepiej zrozumieć temat i rozwijać swoje umiejętności. Pisząc dla jscwiczenia.pl, koncentruję się na dostarczaniu aktualnych i rzetelnych informacji, które są zrozumiałe nawet dla osób dopiero zaczynających swoją przygodę z programowaniem. Staram się porównywać różne źródła, śledzić najnowsze trendy i organizować wiedzę w sposób, który ułatwia naukę. Moim celem jest, aby każdy mógł znaleźć tu przydatne materiały, które pomogą mu w budowaniu kariery w programowaniu webowym.

Napisz komentarz