Jaki jest skuteczny sposób na implementację wzorca singleton w Javie? [Zamknięte]


812

Jaki jest skuteczny sposób na implementację wzorca singleton w Javie?


1
„Jaki jest skuteczny sposób na implementację wzorca singleton w Javie?” proszę zdefiniować efektywne.
Marian Paździoch

medium.com/@kevalpatel2106/… . Jest to pełny artykuł na temat tego, jak osiągnąć bezpieczeństwo nitki, odbicia i serializacji we wzorze singletonu. Jest to dobre źródło do zrozumienia korzyści i ograniczeń klasy singleton.
Keval Patel

Jak wskazuje Joshua Bloch w Effective Java, najlepszym rozwiązaniem jest enum singleton. Tutaj sklasyfikowałem różne wdrożenia jako leniwe / chętne itp.
isaolmez

Odpowiedzi:


782

Użyj wyliczenia:

public enum Foo {
    INSTANCE;
}

Joshua Bloch wyjaśnił to podejście w swoim przemówieniu na temat efektywnego ponownego ładowania Java na Google I / O 2008: link do wideo . Zobacz także slajdy 30-32 jego prezentacji ( efektywna_java_reloaded.pdf ):

Właściwy sposób na wdrożenie singletonu z możliwością szeregowania

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Edit: część online "Effective Java" mówi:

„Podejście to jest funkcjonalnie równoważne z podejściem publicznym, z wyjątkiem tego, że jest bardziej zwięzłe, zapewnia mechanizmy serializacji za darmo i zapewnia żelazną gwarancję przeciwko wielokrotnemu tworzeniu instancji, nawet w obliczu wyrafinowanych ataków serializacji lub odbicia. Chociaż takie podejście ma jednak do powszechnego przyjęcia, jednoczęściowy typ wyliczania jest najlepszym sposobem na wdrożenie singletonu ”.


203
Myślę, że ludzie powinni zacząć postrzegać wyliczenia jako klasę z funkcją. jeśli możesz wymienić instancje swojej klasy w czasie kompilacji, użyj enum.
Amir Arad,

7
Ja osobiście często nie potrzebuję bezpośrednio używać wzorca singletonu. Czasami używam iniekcji zależności wiosny w kontekście aplikacji, który zawiera tak zwane singletony. Moje klasy narzędzi zawierają zwykle tylko metody statyczne i nie potrzebuję ich żadnych instancji.
Stephen Denne

3
Cześć, Czy ktoś może mi powiedzieć, jak tego rodzaju singleton można wyśmiewać i testować w testowych przypadkach. Próbowałem zamienić fałszywą instancję singletonu dla tego typu, ale nie mogłem.
Ashish Sharma

29
Myślę, że to ma sens, ale nadal mi się nie podoba. Jak stworzyłbyś singletona, który przedłuży kolejną klasę? Jeśli używasz wyliczenia, nie możesz.
chharvey

11
@bvdb: Jeśli chcesz dużo elastyczności, to już spieprzyłeś wprowadzając singletona w pierwszej kolejności. Możliwość utworzenia niezależnej instancji, gdy jej potrzebujesz, jest sama w sobie bezcenna.
cHao

233

W zależności od zastosowania istnieje kilka „poprawnych” odpowiedzi.

Od wersji java5 najlepszym sposobem na to jest użycie wyliczenia:

public enum Foo {
   INSTANCE;
}

Przed java5 najprostszym przypadkiem jest:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

Omówmy kod. Po pierwsze, chcesz, aby klasa była ostateczna. W tym przypadku użyłem finalsłowa kluczowego, aby poinformować użytkowników, że jest ono ostateczne. Następnie musisz ustawić konstruktora jako prywatny, aby uniemożliwić użytkownikom tworzenie własnego Foo. Zgłoszenie wyjątku od konstruktora uniemożliwia użytkownikom użycie refleksji w celu utworzenia drugiego Foo. Następnie tworzysz private static final Foopole do przechowywania jedynej instancji i public static Foo getInstance()metodę jej zwracania. Specyfikacja Java zapewnia, że ​​konstruktor jest wywoływany tylko wtedy, gdy klasa jest używana po raz pierwszy.

Jeśli masz bardzo duży obiekt lub ciężki kod konstrukcyjny ORAZ masz również inne dostępne metody statyczne lub pola, które mogą być użyte przed potrzebą wystąpienia instancji, wtedy i tylko wtedy musisz użyć leniwej inicjalizacji.

Możesz użyć a, private static classaby załadować instancję. Kod wyglądałby wtedy następująco:

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

Ponieważ linia private static final Foo INSTANCE = new Foo();jest wykonywana tylko wtedy, gdy faktycznie używana jest klasa FooLoader, zajmuje się ona leniwą instancją i gwarantuje, że jest bezpieczna dla wątków.

Jeśli chcesz również móc serializować swój obiekt, musisz upewnić się, że deserializacja nie utworzy kopii.

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Metoda readResolve()upewni się, że zostanie zwrócona jedyna instancja, nawet jeśli obiekt został zserializowany w poprzednim uruchomieniu programu.


32
Kontrola odbicia jest bezużyteczna. Jeśli inny kod używa refleksji nad szeregami prywatnymi, oznacza to koniec gry. Nie ma powodu, aby nawet próbować działać poprawnie przy takim niewłaściwym użyciu. A jeśli spróbujesz, i tak będzie to niekompletna „ochrona”, tylko dużo zmarnowanego kodu.
Wouter Coekaerts

5
> „Po pierwsze, chcesz, aby klasa była ostateczna”. Czy mógłby ktoś rozwinąć tę kwestię?
PlagueHammer

2
Ochrona deserializacji jest całkowicie zepsuta (myślę, że wspomniano o tym w Effective Java 2nd Ed).
Tom Hawtin - tackline

9
-1 to absolutnie nie najprostszy przypadek, jest przemyślany i niepotrzebnie złożony. Spójrz na odpowiedź Jonathana na najprostsze rozwiązanie, które wystarcza we 99,9% wszystkich przypadków.
Michael Borgwardt,

2
Jest to przydatne, gdy singleton musi odziedziczyć po nadklasie. W tym przypadku nie można użyć wzorca singletonu enum, ponieważ wyliczenia nie mogą mieć nadklasy (mogą jednak implementować interfejsy). Na przykład Google Guava używa statycznego pola końcowego, gdy wzorzec singletonu enum nie jest opcją: code.google.com/p/guava-libraries/source/browse/trunk/guava/src/…
Etienne Neveu

139

Oświadczenie: Właśnie streściłem wszystkie niesamowite odpowiedzi i napisałem to własnymi słowami.


Wdrażając Singleton mamy 2 opcje
1. Leniwe ładowanie
2. Wczesne ładowanie

Leniwe ładowanie dodaje nieco narzutu (szczerze mówiąc), więc używaj go tylko wtedy, gdy masz bardzo duży obiekt lub ciężki kod konstrukcyjny ORAZ masz również inne dostępne metody statyczne lub pola, które mogą być użyte przed potrzebą wystąpienia, wtedy i tylko wtedy musisz użyć leniwej inicjalizacji. W przeciwnym razie dobrym wyborem jest wczesne ładowanie.

Najprostszym sposobem wdrożenia Singleton jest

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

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

Wszystko jest dobrze, z wyjątkiem wczesnego singletona. Spróbujmy leniwie ładowanego singletona

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

Jak dotąd tak dobrze, ale nasz bohater nie przetrwa, walcząc samotnie z wieloma złymi wątkami, które chcą wielu instancji naszego bohatera. Więc chrońmy go przed złym wielowątkowością

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

ale nie wystarczy chronić bohatera, naprawdę !!! To najlepsze, co możemy / powinniśmy zrobić, aby pomóc naszemu bohaterowi

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

Nazywa się to „idiomem podwójnie sprawdzanego blokowania”. Łatwo zapomnieć o niestabilnym stwierdzeniu i trudno zrozumieć, dlaczego jest to konieczne.
Aby uzyskać szczegółowe informacje: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Teraz jesteśmy pewni złej nici, ale co z okrutną serializacją? Musimy się upewnić, że nawet podczas de-serializacji nie powstaje żaden nowy obiekt

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

Metoda readResolve()upewni się, że zostanie zwrócona jedyna instancja, nawet jeśli obiekt został zserializowany w poprzednim uruchomieniu naszego programu.

Wreszcie dodaliśmy wystarczającą ochronę przed wątkami i serializacją, ale nasz kod wygląda na nieporęczny i brzydki. Dajmy naszemu bohaterowi przeróbkę

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Tak, to nasz bardzo ten sam bohater :)
Ponieważ linia private static final Foo INSTANCE = new Foo();jest wykonywana tylko wtedy, gdy klasa FooLoaderjest rzeczywiście używana, to zajmuje się leniwą instancją,

i czy gwarantuje to, że jest wątkowo bezpieczny.

Dotarliśmy do tej pory, oto najlepszy sposób, aby osiągnąć wszystko, co zrobiliśmy, to najlepszy możliwy sposób

 public enum Foo {
       INSTANCE;
   }

Które wewnętrznie będą traktowane jak

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

Otóż ​​to! Nigdy więcej strachu przed serializacją, wątkami i brzydkim kodem. Również singleton ENUMS jest leniwie inicjowany .

Podejście to jest funkcjonalnie równoważne z podejściem publicznym, z wyjątkiem tego, że jest bardziej zwięzłe, zapewnia maszynerię do serializacji za darmo i zapewnia żelazną gwarancję przeciwko wielokrotnemu tworzeniu instancji, nawet w obliczu wyrafinowanych ataków serializacji lub odbicia. Chociaż podejście to nie zostało jeszcze powszechnie przyjęte, jednoelementowy typ wyliczenia jest najlepszym sposobem na wdrożenie singletonu.

-Joshua Bloch w „Skutecznej Javie”

Teraz mogłeś zrozumieć, dlaczego ENUMS są uważane za najlepszy sposób na wdrożenie Singleton i dziękuję za cierpliwość :)
Zaktualizowałem go na moim blogu .


3
Tylko wyjaśnienie: singletony realizowane za pomocą enum są inicjowane leniwie. Szczegóły tutaj: stackoverflow.com/questions/16771373/…

2
świetna odpowiedź. ostatnią rzeczą, przesłonić metodę klonowania, aby zgłosić wyjątek.
riship89,

2
@xyz ładne wyjaśnienia, bardzo podobał i dowiedział się bardzo łatwo i nigdy nie zapomniał tej nadziei
Premraj

1
Jedna z najlepszych odpowiedzi, jakie kiedykolwiek widziałem na temat przepełnienia stosu. Dzięki!
Shay Tsadok

1
Nie jest to problem serializacji z użyciem teksty stałe jako pojedyncza: Wszelkie wartości pól członkiem są nie w odcinkach, a zatem nie przywrócono. Zobacz specyfikację Java Object Serialization Specification, wersja 6.0 . Kolejny problem: Nie wersjonowania - wszystkie typy wyliczeniowe mają stałe serialVersionUIDz 0L. Trzeci problem: brak możliwości dostosowania: Wszelkie metody writeObject, readObject, readObjectNoData, writeReplace i readResolve zdefiniowane przez typy wyliczeniowe są ignorowane podczas serializacji i deserializacji.
Basil Bourque

124

Rozwiązanie opublikowane przez Stu Thompson jest ważne w Javie 5.0 i nowszych. Ale wolałbym go nie używać, ponieważ uważam, że jest on podatny na błędy.

Łatwo zapomnieć o niestabilnym stwierdzeniu i trudno zrozumieć, dlaczego jest to konieczne. Bez niestabilności kod ten nie byłby już bezpieczny dla wątków z powodu podwójnie sprawdzonego blokowania antipattern. Więcej informacji na ten temat można znaleźć w paragrafie 16.2.4 „ Współbieżność Java” . W skrócie: Ten wzorzec (przed Javą 5.0 lub bez zmiennej instrukcji) może zwrócić odwołanie do obiektu Bar, który jest (nadal) w niepoprawnym stanie.

Ten wzór został wymyślony w celu optymalizacji wydajności. Ale to naprawdę nie jest już poważny problem. Poniższy leniwy kod inicjujący jest szybki i - co ważniejsze - łatwiejszy do odczytania.

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}

1
Słusznie! Po prostu czuję się swobodnie z niestabilnością i jej użyciem. Aha, i trzy okrzyki dla JCiP.
Stu Thompson,

1
Och, to najwyraźniej takie podejście poparł William Pugh, znany z FindBugz.
Stu Thompson,

4
@Stu Pierwsze wydanie Effective Java (Copyright 2001) szczegółowo opisuje ten wzór w punkcie 48.
Pascal Thivent

9
@Bno: A co z ustawieniem konstruktora na prywatny?
xyz

2
@ AlikElzin-kilaka Niezupełnie. Instancja jest tworzona w fazie ładowania klasy dla BarHolder , która jest opóźniona do pierwszej potrzeby. Konstruktor Bar może być tak skomplikowany, jak chcesz, ale nie zostanie wywołany do pierwszego getBar(). (A jeśli getBarnazywa się to „za wcześnie”, to napotkasz ten sam problem bez względu na to, w jaki sposób implementowane są pojedyncze). Możesz zobaczyć leniwe ładowanie kodu powyżej: pastebin.com/iq2eayiR
Ti Strga

95

Wątek bezpieczny w Javie 5+:

class Foo {
    private static volatile Bar bar = null;
    public static Bar getBar() {
        if (bar == null) {
            synchronized(Foo.class) {
                if (bar == null)
                    bar = new Bar(); 
            }
        }
        return bar;
    }
}

EDYCJA : Zwróć uwagę na volatilemodyfikator tutaj. :) Jest to ważne, ponieważ bez niego JMM (Java Memory Model) nie gwarantuje, że inne wątki zobaczą zmiany jego wartości. Synchronizacja nie dba o to - tylko szereguje dostęp do tego bloku kodu.

EDYCJA 2 : Odpowiedź @Bno szczegółowo opisuje podejście zalecane przez Billa Pugh (FindBugs) i jest lepsza. Przeczytaj i zagłosuj na jego odpowiedź.


1
Gdzie mogę dowiedzieć się więcej o modyfikatorze lotnym?
jedenaście81


2
Myślę, że ważne jest, aby wspomnieć o atakach refleksyjnych. To prawda, że ​​większość programistów nie musi się tym martwić, ale wydaje się, że takie przykłady (w porównaniu z singletonami opartymi na Enum) powinny zawierać kod chroniący przed atakami z wieloma instancjami lub po prostu umieścić zastrzeżenie wskazujące na takie możliwości.
luis.espinal

2
Nie jest tu potrzebne zmienne słowo kluczowe - synchronizacja zapewnia zarówno wzajemne wykluczenie, jak i widoczność pamięci.
Hemant,

2
Po co męczyć się tym wszystkim w Javie 5+? Rozumiem, że metoda enum zapewnia zarówno bezpieczeństwo wątków, jak i leniwą inicjalizację. Jest to również o wiele prostsze ... Ponadto, jeśli chcesz uniknąć wyliczenia, nadal zapobiegałbym podejściu do zagnieżdżonej klasy statycznej ...
Alexandros

91

Zapomnij o leniwej inicjalizacji , to jest zbyt problematyczne. To jest najprostsze rozwiązanie:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

21
zmienna instancji singleton może być również ostateczna. np. prywatny statyczny finał A singleton = nowy A ();
jatanp

19
Jest to skutecznie leniwa inicjalizacja, ponieważ statyczny singleton nie zostanie utworzony, dopóki klasa nie zostanie załadowana, a klasa nie zostanie załadowana, dopóki nie będzie potrzebna (co będzie mniej więcej w momencie pierwszego odwołania się do metody getInstance ()).
Dan Dyer

7
Jeśli klasa A zostanie załadowana na długo przed tym, zanim chcesz utworzyć instancję statyczną, możesz owinąć statyczną statyczną klasę wewnętrzną, aby oddzielić inicjalizację klasy.
Tom Hawtin - tackline

3
zgadzam się, że ta odpowiedź jest najprostsza i Anirudhan, nie ma potrzeby ogłaszania instancji jako ostatecznej. Żaden inny wątek nie uzyska dostępu do klasy podczas inicjowania elementów statycznych. kompilator gwarantuje to, innymi słowy, cała statyczna inicjalizacja odbywa się w sposób zsynchronizowany - tylko jeden wątek.
inor

4
To podejście ma jedno ograniczenie: Konstruktor nie może zgłosić wyjątku.
wangf

47

Upewnij się, że naprawdę tego potrzebujesz. Poszukaj w Google „singleton anti-pattern”, aby zobaczyć argumenty przeciwko niemu. Wydaje mi się, że nie ma w tym nic złego, ale jest to tylko mechanizm ujawniania niektórych globalnych zasobów / danych, więc upewnij się, że jest to najlepszy sposób. W szczególności uważam, że wstrzykiwanie zależności jest bardziej przydatne, szczególnie jeśli używasz testów jednostkowych, ponieważ DI pozwala na użycie kpionych zasobów do celów testowych.


można wstrzyknąć wartości makiety z tradycyjną metodą też, ale chyba jej nie standard / srping drogę więc jego dodatkowej pracy z tylko zysk będącego starszych kod ...
tgkprog

21

Jestem zaskoczony niektórymi odpowiedziami sugerującymi DI jako alternatywę dla używania singletonów; są to niepowiązane pojęcia. Za pomocą DI można wstrzykiwać instancje singletonowe lub nie singletonowe (np. Na wątek). Przynajmniej jest to prawdą, jeśli używasz Spring 2.x, nie mogę mówić o innych frameworkach DI.

Tak więc moją odpowiedzią na PO byłoby (we wszystkich, z wyjątkiem najbardziej trywialnego przykładowego kodu), aby:

  1. Użyj więc środowiska DI, takiego jak Spring
  2. Ustaw jako część konfiguracji DI, czy zależności są singletonami, zasięgiem zapytań, zasięgiem sesji lub czymkolwiek.

Takie podejście zapewnia niezłą architekturę odsprzężoną (a zatem elastyczną i testowalną), w której użycie singletonu jest łatwo odwracalnym szczegółem implementacji (pod warunkiem, że wszystkie singletony, których używasz, są oczywiście bezpieczne dla wątków).


4
Być może dlatego, że ludzie się z tobą nie zgadzają. Nie doceniłem cię, ale nie zgadzam się: myślę, że DI można wykorzystać do rozwiązania tych samych problemów, co singletony. Opiera się to na zrozumieniu słowa „singleton” w znaczeniu „obiekt z pojedynczą instancją, do którego można uzyskać bezpośredni dostęp poprzez globalną nazwę”, a nie tylko „obiekt z jedną instancją”, co może być nieco podstępne.
Tom Anderson,

2
Aby nieco to rozwinąć, zastanów się, TicketNumbererktóra musi mieć jedną globalną instancję i gdzie chcesz napisać klasę TicketIssuerzawierającą wiersz kodu int ticketNumber = ticketNumberer.nextTicketNumber();. W tradycyjnym myśleniu singletonowym poprzednia linia kodu musiałaby być podobna TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. W myśleniu DI klasa miałaby konstruktora podobnego do public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.
Tom Anderson,

2
Nazywanie tego konstruktora staje się problemem kogoś innego. Struktura DI zrobiłaby to z jakąś globalną mapą; zrobi to ręcznie zbudowana architektura DI, ponieważ mainmetoda aplikacji (lub jeden z jej stronników) stworzyłaby zależność, a następnie wywołała konstruktor. Zasadniczo użycie zmiennej globalnej (lub metody globalnej) jest po prostu prostą formą wzorca przerażającego lokalizatora usług i można ją zastąpić wstrzykiwaniem zależności, tak jak każde inne użycie tego wzorca.
Tom Anderson,

@TomAnderson Jestem naprawdę zdezorientowany, dlaczego ludzie „boją się” schematu lokalizatora usług. Myślę, że w większości przypadków jest to przesada lub w najlepszym wypadku niepotrzebna, jednak są pozornie przydatne przypadki. Przy mniejszej liczbie parametrów zdecydowanie preferowane jest DI, ale wyobraź sobie 20+. Powiedzenie, że kod nie jest zorganizowany, nie jest poprawnym argumentem, ponieważ czasami grupowanie parametrów po prostu nie ma sensu. Ponadto, z punktu widzenia testów jednostkowych, nie dbam o testowanie usługi, tylko logikę biznesową, a jeśli jest poprawnie zakodowana, byłoby to łatwe. Potrzebę tę widziałem tylko w projektach na bardzo dużą skalę.
Brandon Ling,

20

Naprawdę zastanów się, dlaczego potrzebujesz singletonu przed jego napisaniem. Trwa quasi-religijna debata na temat korzystania z nich, na którą można dość łatwo natknąć się, jeśli będziesz wyszukiwać singletony w Javie.

Osobiście staram się unikać singletonów tak często, jak to możliwe, z wielu powodów, z których większość można znaleźć w Google singletonach. Wydaje mi się, że dość często singletony są nadużywane, ponieważ są one łatwe do zrozumienia dla wszystkich, są wykorzystywane jako mechanizm wprowadzania „globalnych” danych do projektu OO i są używane, ponieważ łatwo jest ominąć zarządzanie cyklem życia obiektu (lub naprawdę myśląc o tym, jak możesz zrobić A od środka B). Spójrz na takie rzeczy jak Inversion of Control (IoC) lub Dependency Injection (DI), aby uzyskać ładne pole środkowe.

Jeśli naprawdę go potrzebujesz, to wikipedia ma dobry przykład poprawnej implementacji singletona.


Zgoda. Jest to bardziej podstawowa klasa, która uruchamia resztę aplikacji, a jeśli zostanie zduplikowana, skończy się to całkowitym chaosem (tj. Pojedynczy dostęp do zasobu lub egzekwowanie bezpieczeństwa). Przekazywanie danych globalnych w całej aplikacji jest dużą czerwoną flagą sprzęgającą. Użyj go, gdy uznasz, że naprawdę go potrzebujesz.
Salvador Valencia,

16

Poniżej przedstawiono 3 różne podejścia

1) Wylicz

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) Podwójnie zaznaczone Blokowanie / Leniwe ładowanie

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3) Metoda statyczna fabryczna

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

13

Używam Spring Framework do zarządzania moimi singletonami. Nie wymusza „singletonowości” klasy (czego tak naprawdę nie można zrobić, jeśli w grę wchodzi wiele modułów ładujących klasy), ale zapewnia naprawdę łatwy sposób budowania i konfigurowania różnych fabryk do tworzenia różnych typów obiektów.


11

Wikipedia ma kilka przykładów singletonów, także w Javie. Implementacja Java 5 wygląda całkiem kompletnie i jest bezpieczna dla wątków (zastosowano podwójnie sprawdzone blokowanie).


11

Wersja 1:

public class MySingleton {
    private static MySingleton instance = null;
    private MySingleton() {}
    public static synchronized MySingleton getInstance() {
        if(instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }
}

Leniwe ładowanie, wątek bezpieczny z blokowaniem, niska wydajność z powodu synchronized.

Wersja 2:

public class MySingleton {
    private MySingleton() {}
    private static class MySingletonHolder {
        public final static MySingleton instance = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHolder.instance;
    }
}

Leniwe ładowanie, bezpieczny wątek z nieblokującą, wysoką wydajnością.


10

Jeśli nie potrzebujesz leniwego ładowania, po prostu spróbuj

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

    private Singleton() {}

    public static Singleton getInstance() { return Singleton.INSTANCE; }

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

Jeśli chcesz leniwego ładowania i chcesz, aby Twój Singleton był bezpieczny dla wątków, wypróbuj wzór podwójnego sprawdzania

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

        public static Singleton getInstance() { 
              if(null == instance) {
                  synchronized(Singleton.class) {
                      if(null == instance) {
                          instance = new Singleton();
                      }
                  }
               }
               return instance;
        }

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

Ponieważ wzorzec podwójnego sprawdzania nie działa (z powodu problemów z kompilatorami, nie wiem nic więcej na ten temat.), Możesz również spróbować zsynchronizować całą metodę getInstance lub utworzyć rejestr dla wszystkich Singletonów.


2
Pierwsza wersja jest najlepsza. Zakładając, że klasa nie robi nic poza dostarczeniem singletonu, wówczas zazwyczaj będzie on tworzony w mniej więcej tym samym punkcie co ten w drugiej wersji z powodu opóźnionego ładowania klasy.
Dan Dyer

1
Podwójne sprawdzanie jest bezcelowe w przypadku statycznego. I dlaczego upubliczniłeś chronioną metodę klonowania?
Tom Hawtin - tackline

1
-1 twoja wersja podwójnie sprawdzonego blokowania jest zepsuta.
assylias

5
Musisz także zmienić swój singleton na zmiennyvolatile
MyTitle

Pierwsza wersja jest leniwa i bezpieczna dla wątków.
Miha_x64

9

Powiedziałbym Enum singleton

Singleton używający enum w Javie jest ogólnie sposobem deklarowania enum singleton. Enum singleton może zawierać zmienną instancji i metodę instancji. Dla uproszczenia należy również pamiętać, że jeśli używasz dowolnej metody instancji, musisz zapewnić bezpieczeństwo wątków tej metody, jeśli w ogóle wpływa ona na stan obiektu.

Zastosowanie wyliczenia jest bardzo łatwe do wdrożenia i nie ma żadnych wad związanych z serializowanymi obiektami, które należy obejść na inne sposoby.

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

Możesz uzyskać do niego dostęp Singleton.INSTANCEznacznie łatwiej niż wywoływanie getInstance()metody w Singleton.

1.12 Serializacja stałych enum

Stałe enum są serializowane inaczej niż zwykłe obiekty szeregowalne lub eksternalizowalne. Szeregowa postać stałej wyliczeniowej składa się wyłącznie z jej nazwy; wartości pola stałej nie są obecne w formularzu. Aby serializować stałą enum, ObjectOutputStreamzapisuje wartość zwróconą przez metodę nazwy stałej enum. Aby deserializować stałą wyliczania, ObjectInputStreamodczytuje stałą nazwę ze strumienia; deserializowana stała jest następnie uzyskiwana przez wywołanie java.lang.Enum.valueOfmetody, przekazując typ argumentu stałej wraz z otrzymaną nazwą stałą jako argumentami. Podobnie jak inne obiekty możliwe do serializacji lub eksternalizacji, stałe wyliczeniowe mogą działać jako obiekty docelowe referencji pojawiających się później w strumieniu serializacji.

Proces, w którym enum stałe są szeregowane nie można dostosować: każda klasa specyficzne writeObject, readObject, readObjectNoData, writeReplace, i readResolvemetody zdefiniowane przez typów enum są ignorowane podczas serializacji i deserializacji. Podobnie wszelkie serialPersistentFieldslub serialVersionUIDpolowe deklaracje są ignorowane - wszystkie typy wyliczeniowe mają stałe serialVersionUIDz 0L. Dokumentowanie szeregowalnych pól i danych dla typów wyliczeniowych nie jest konieczne, ponieważ nie ma zmian w rodzaju wysyłanych danych.

Cytat z dokumentów Oracle

Innym problemem związanym z konwencjonalnymi singletonami jest to, że po wdrożeniu Serializable interfejsu nie są już Singletonami, ponieważ readObject()metoda zawsze zwraca nową instancję, taką jak konstruktor w Javie. Można tego uniknąć, używając readResolve()i odrzucając nowo utworzoną instancję, zastępując singletonem, jak poniżej

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

Może to stać się jeszcze bardziej skomplikowane, jeśli klasa Singleton utrzyma stan, ponieważ musisz sprawić, by stały się przejściowe, ale w Enum Singleton, Serializacja jest gwarantowana przez JVM.


Dobra lektura

  1. Singleton Pattern
  2. Wyliczenia, singletony i deserializacja
  3. Podwójnie sprawdzone ryglowanie i wzór Singleton

8
There are 4 ways to create a singleton in java.

1- eager initialization singleton

    public class Test{
        private static final Test test = new Test();
        private Test(){}
        public static Test getTest(){
            return test;
        }
    

2- lazy initialization singleton (thread safe)

    public class Test {
         private static volatile Test test;
         private Test(){}
         public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();
                }
            }
         }

        return test;
    


3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)

    public class Test {

        private Test(){}

        private static class TestHolder{
            private static final Test test = new Test();
        }

        public static Test getInstance(){
            return TestHolder.test;
        }
    }

4- enum singleton
      public enum MySingleton {
        INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}

(1) nie jest chętny, jest leniwy ze względu na mechanizm ładowania klasy JVM.
Miha_x64

@ Miha_x64, kiedy powiedziałem chętne ładowanie, powiedziałem chętne inicjowanie. Jeśli uważasz, że oba są takie same, to chętne ładowanie. Być może powinieneś napisać książkę i poprawić błędy popełnione przez poprzednich autorów, takich jak Joshua Bloch.
Dheeraj Sachan

Skuteczna Java to świetna książka, ale zdecydowanie wymaga edycji.
Miha_x64,

@ Miha_x64, co chętnie ładuje, czy możesz wyjaśnić na przykładzie
Dheeraj Sachan

Robienie czegoś „niecierpliwie” oznacza „tak szybko, jak to możliwe”. Na przykład Hibernacja obsługuje ładowanie relacji chętnie, jeśli jest to jawnie wymagane.
Miha_x64,

8

Może się trochę spóźnić do tej gry, ale istnieje wiele niuansów przy wdrażaniu singletonu. Wzór uchwytu nie może być stosowany w wielu sytuacjach. I IMO, gdy używasz zmiennej - powinieneś również użyć zmiennej lokalnej. Zacznijmy od początku i powtórzmy problem. Zobaczysz o co mi chodzi.


Pierwsza próba może wyglądać mniej więcej tak:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

Tutaj mamy klasę MySingleton, która ma prywatny element statyczny o nazwie INSTANCE i publiczną metodę statyczną o nazwie getInstance (). Przy pierwszym wywołaniu getInstance () element INSTANCE ma wartość NULL. Przepływ następnie przejdzie w stan tworzenia i utworzy nową instancję klasy MySingleton. Kolejne wywołania getInstance () stwierdzą, że zmienna INSTANCE jest już ustawiona, a zatem nie utworzą kolejnej instancji MySingleton. Dzięki temu istnieje tylko jedna instancja MySingleton, która jest współużytkowana przez wszystkie osoby wywołujące getInstance ().

Ale ta implementacja ma problem. Aplikacje wielowątkowe będą miały warunek wyścigu podczas tworzenia pojedynczej instancji. Jeśli wiele wątków wykonania trafi do metody getInstance () w tym samym czasie (lub w pobliżu), każdy z nich zobaczy element INSTANCE jako zerowy. Spowoduje to, że każdy wątek utworzy nową instancję MySingleton, a następnie ustawi element członkowski INSTANCE.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

W tym celu użyliśmy słowa kluczowego synchronizowanego w sygnaturze metody, aby zsynchronizować metodę getInstance (). To z pewnością naprawi naszą rasę. Wątki będą teraz blokować i wprowadzać metodę pojedynczo. Ale stwarza to również problem z wydajnością. Ta implementacja nie tylko synchronizuje tworzenie pojedynczej instancji, ale także synchronizuje wszystkie wywołania getInstance (), w tym odczyty. Odczyty nie muszą być synchronizowane, ponieważ po prostu zwracają wartość INSTANCJA. Ponieważ odczyty będą stanowić większość naszych wywołań (pamiętaj, że tworzenie wystąpień następuje tylko przy pierwszym wywołaniu), poniesiemy niepotrzebne pogorszenie wydajności, synchronizując całą metodę.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

W tym miejscu przenieśliśmy synchronizację z podpisu metody do zsynchronizowanego bloku, który otacza tworzenie instancji MySingleton. Ale czy to rozwiązuje nasz problem? Cóż, nie blokujemy już odczytów, ale zrobiliśmy też krok do tyłu. Wiele wątków trafi do metody getInstance () w tym samym czasie lub mniej więcej w tym samym czasie i wszystkie zobaczą element INSTANCE jako null. Następnie uderzą w zsynchronizowany blok, w którym otrzymamy blokadę i utworzymy instancję. Kiedy ten wątek wyjdzie z bloku, pozostałe wątki będą walczyć o blokadę, a jeden wątek po kolei spadnie przez blok i utworzy nowe wystąpienie naszej klasy. Jesteśmy więc dokładnie tam, gdzie zaczęliśmy.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Tutaj wystawiamy kolejny czek WEWNĄTRZ bloku. Jeśli element INSTANCE został już ustawiony, pomiń inicjalizację. Nazywa się to blokowaniem z podwójną kontrolą.

To rozwiązuje nasz problem wielokrotnego tworzenia wystąpień. Ale po raz kolejny nasze rozwiązanie stanowi kolejne wyzwanie. Inne wątki mogą nie „widzieć”, że członek INSTANCE został zaktualizowany. Wynika to z tego, jak Java optymalizuje operacje pamięci. Wątki kopiują oryginalne wartości zmiennych z pamięci głównej do pamięci podręcznej procesora. Zmiany wartości są następnie zapisywane w pamięci podręcznej i odczytywane z niej. Jest to funkcja Java zaprojektowana w celu optymalizacji wydajności. Ale to stwarza problem dla naszej implementacji singletonu. Drugi wątek - przetwarzany przez inny procesor lub rdzeń przy użyciu innej pamięci podręcznej - nie zobaczy zmian dokonanych przez pierwszy. Spowoduje to, że drugi wątek zobaczy element INSTANCE jako zerowy, co spowoduje utworzenie nowej instancji naszego singletonu.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Rozwiązujemy to za pomocą niestabilnego słowa kluczowego z deklaracji członka INSTANCE. Dzięki temu kompilator zawsze będzie czytał i zapisywał w pamięci głównej, a nie w pamięci podręcznej procesora.

Ale ta prosta zmiana kosztuje. Ponieważ omijamy pamięć podręczną procesora, za każdym razem, gdy będziemy działać na niestabilnym elemencie INSTANCE, osiągniemy spadek wydajności - co robimy 4 razy. Dokładnie sprawdzamy istnienie (1 i 2), ustawiamy wartość (3), a następnie zwracamy wartość (4). Można argumentować, że ta ścieżka jest przypadkiem marginesowym, ponieważ tworzymy instancję tylko podczas pierwszego wywołania metody. Być może uderzenie wydajności w tworzenie jest dopuszczalne. Ale nawet nasz główny przypadek użycia, czyta, będzie działał na elemencie lotnym dwukrotnie. Raz, aby sprawdzić istnienie, i jeszcze raz, aby zwrócić jego wartość.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

Ponieważ uderzenie wydajności wynika z działania bezpośrednio na elemencie lotnym, ustawmy zmienną lokalną na wartość zmiennej i działajmy na zmiennej lokalnej. Spowoduje to zmniejszenie liczby operacji na lotności, a tym samym odzyskanie części utraconych wyników. Zauważ, że musimy ponownie ustawić naszą zmienną lokalną, kiedy wchodzimy do bloku synchronicznego. Zapewnia to, że jest na bieżąco z wszelkimi zmianami, które miały miejsce, gdy czekaliśmy na zamek.

Ostatnio napisałem o tym artykuł. Dekonstrukcja Singletona . Możesz znaleźć więcej informacji na temat tych przykładów i przykładowy wzór „posiadacza”. Istnieje również przykład z realnego świata pokazujący podejście do lotności z podwójną kontrolą. Mam nadzieję że to pomoże.


Czy możesz wyjaśnić, dlaczego BearerToken instancew twoim artykule nie ma static? A co to jest result.hasExpired()?
Woland,

A co class MySingleton- może powinno być final?
Woland

1
@Woland BearerTokenInstancja nie jest statyczna, ponieważ jest częścią BearerTokenFactory -, który jest skonfigurowany z określonym serwerem autoryzacji. Może być wiele BearerTokenFactoryobiektów - każdy z nich ma własną „pamięć podręczną” BearerToken, którą rozdaje, aż do wygaśnięcia. Ta hasExpired()metoda BeraerTokenjest wywoływana w get()metodzie fabrycznej, aby upewnić się, że nie rozdaje przeterminowanego tokena. Jeśli upłynie termin ważności, na serwerze autoryzacji zostanie zażądany nowy token. Akapit po bloku kodu wyjaśnia to bardziej szczegółowo.
Michael Andrews,

4

Oto jak zaimplementować prosty singleton:

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */ 
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Oto jak prawidłowo leniwie stworzyć singleton:

public class Singleton {
    // The constructor must be private to prevent external instantiation   
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only 
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE 
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

Oba są leniwe, zakładając, że jedyne, czego potrzebujesz od singletonu, to jego instancja.
Miha_x64

@ Miha_x64 pierwszy przypadek utworzy singleton, gdy JVM zainicjuje klasę, drugi przypadek utworzy singleton tylko podczas wywołania getInstance(). Ale tak naprawdę, jeśli nie masz żadnych innych metod statycznych w swojej klasie Singletoni tylko dzwonisz, getInstance()nie ma prawdziwej różnicy.
Nicolas Filotto

3

Potrzebujesz podwójnego sprawdzania idiomu, jeśli chcesz leniwie ładować zmienną instancji klasy. Jeśli musisz leniwie ładować zmienną statyczną lub singletona, potrzebujesz inicjalizacji na żądanie .

Ponadto, jeśli singleton musi być serilizowalny, wszystkie inne pola muszą być przejściowe i należy zaimplementować metodę readResolve () w celu utrzymania niezmienności obiektu singletonu. W przeciwnym razie, za każdym razem, gdy obiekt zostanie przekształcony z postaci szeregowej, zostanie utworzona nowa instancja obiektu. ReadResolve () zastępuje nowy obiekt odczytany przez readObject (), co zmusiło ten nowy obiekt do wyrzucania elementów bezużytecznych, ponieważ nie odwołuje się do niego żadna zmienna.

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 

3

Różne sposoby tworzenia obiektu singleton:

  1. Według Joshua Blocha - Enum byłby najlepszy.

  2. możesz także użyć blokady podwójnej kontroli.

  3. Można stosować nawet wewnętrzną klasę statyczną.


3

Enum singleton

Najprostszym sposobem na wdrożenie Singletona, który jest bezpieczny dla wątków, jest użycie Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Ten kod działa od czasu wprowadzenia Enum w Javie 1.5

Podwójnie sprawdzone ryglowanie

Jeśli chcesz zakodować „klasyczny” singleton działający w środowisku wielowątkowym (od Java 1.5), powinieneś go użyć.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class){
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance ;
  }
}

Nie jest to wątkowo bezpieczne przed 1.5, ponieważ implementacja niestabilnego słowa kluczowego była inna.

Wczesne ładowanie Singleton (działa nawet przed Java 1.5)

Ta implementacja tworzy singletona po załadowaniu klasy i zapewnia bezpieczeństwo wątków.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}

2

W przypadku JSE 5.0 i nowszych zastosuj podejście Enum, w przeciwnym razie użyj statycznego uchwytu singletona ((leniwe podejście do ładowania opisane przez Billa Pugha). Późniejsze rozwiązanie jest również bezpieczne dla wątków, nie wymaga specjalnych konstrukcji językowych (tj. Lotnych lub zsynchronizowanych).


2

Innym argumentem często używanym przeciwko Singletonom są problemy z testowalnością. Singletonów nie można łatwo wyśmiewać do celów testowych. Jeśli okaże się to problemem, chciałbym wprowadzić następującą drobną modyfikację:

public class SingletonImpl {

    private static SingletonImpl instance;

    public static SingletonImpl getInstance() {
        if (instance == null) {
            instance = new SingletonImpl();
        }
        return instance;
    }

    public static void setInstance(SingletonImpl impl) {
        instance = impl;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

Dodana setInstancemetoda umożliwia ustawienie implementacji makiety klasy singleton podczas testowania:

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Działa to również w przypadku podejść do wczesnej inicjalizacji:

public class SingletonImpl {

    private static final SingletonImpl instance = new SingletonImpl();

    private static SingletonImpl alt;

    public static void setInstance(SingletonImpl inst) {
        alt = inst;
    }

    public static SingletonImpl getInstance() {
        if (alt != null) {
            return alt;
        }
        return instance;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Ma to tę wadę, że udostępnia tę funkcjonalność również normalnej aplikacji. Inni programiści pracujący nad tym kodem mogą ulec pokusie użycia metody „setInstance” w celu zmiany modyfikacji określonej funkcji, a tym samym zmiany zachowania całej aplikacji, dlatego metoda ta powinna zawierać co najmniej dobre ostrzeżenie w javadoc.

Mimo to, w przypadku możliwości testowania makiet (w razie potrzeby), ujawnienie kodu może być akceptowalną ceną do zapłaty.


1

najprostsza klasa singleton

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}

1
to jest taka sama jak odpowiedź Jonathana poniżej
Inor

1
Duplikat tego rodzeństwa odpowiedzi przez Jonathan pisał pięć lat wcześniej. Zobacz tę odpowiedź, aby uzyskać ciekawe komentarze.
Basil Bourque,

0

Nadal uważam, że po java 1.5 enum jest najlepszą dostępną implementacją singletonu, ponieważ zapewnia również, że nawet w środowiskach wielowątkowych - tworzona jest tylko jedna instancja.

public enum Singleton{ INSTANCE; }

i gotowe!


1
Jest to już wspomniane w innych odpowiedziach lata temu.
Pang

0

Obejrzyj ten post.

Przykłady wzorców projektowych GoF w podstawowych bibliotekach Java

W sekcji „Singleton” najlepszej odpowiedzi:

Singleton (rozpoznawalny za pomocą metod kreacyjnych zwracających za każdym razem tę samą instancję (zwykle samą)

  • java.lang.Runtime # getRuntime ()
  • java.awt.Desktop # getDesktop ()
  • java.lang.System # getSecurityManager ()

Możesz także nauczyć się przykładu Singleton z samych klas rodzimych Java.


0

Najlepszy wzorzec singletonu, jaki kiedykolwiek widziałem, wykorzystuje interfejs dostawcy.

  • Jest ogólny i wielokrotnego użytku
  • Obsługuje leniwe inicjowanie
  • Jest synchronizowany tylko do momentu zainicjowania, a następnie dostawca blokujący zostaje zastąpiony dostawcą nieblokującym.

Patrz poniżej:

public class Singleton<T> implements Supplier<T> {

    private boolean initialized;
    private Supplier<T> singletonSupplier;

    public Singleton(T singletonValue) {
        this.singletonSupplier = () -> singletonValue;
    }

    public Singleton(Supplier<T> supplier) {
        this.singletonSupplier = () -> {
            // The initial supplier is temporary; it will be replaced after initialization
            synchronized (supplier) {
                if (!initialized) {
                    T singletonValue = supplier.get();
                    // Now that the singleton value has been initialized,
                    // replace the blocking supplier with a non-blocking supplier
                    singletonSupplier = () -> singletonValue;
                    initialized = true;
                }
                return singletonSupplier.get();
            }
        };
    }

    @Override
    public T get() {
        return singletonSupplier.get();
    }
}

-3

Czasami proste ”static Foo foo = new Foo(); ” nie wystarczy. Pomyśl tylko o podstawowym wprowadzeniu danych, które chcesz wykonać.

Z drugiej strony musiałbyś zsynchronizować dowolną metodę, która tworzy instancję zmiennej singleton jako takiej. Synchronizacja jako taka nie jest zła, ale może prowadzić do problemów z wydajnością lub blokowania (w bardzo bardzo rzadkich sytuacjach na tym przykładzie. Rozwiązanie to

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

Co się teraz stanie? Klasa jest ładowana za pomocą modułu ładującego klasy. Bezpośrednio po interpretacji klasy z bajtu Array maszyna wirtualna wykonuje blok statyczny {} . to cała tajemnica: blok statyczny jest wywoływany tylko raz, czas załadowania danej klasy (nazwy) danego pakietu przez jeden moduł ładujący klasy.


3
Nie prawda. Zmienne statyczne są inicjowane wraz z blokami statycznymi podczas ładowania klasy. Nie trzeba dzielić deklaracji.
Craig P. Motlin

-5
public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException (“Already instantiated...”);
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

Ponieważ dodaliśmy słowo kluczowe Synchronized przed getInstance, uniknęliśmy warunków wyścigu w przypadku, gdy dwa wątki wywołują getInstance w tym samym czasie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.