it-swarm-eu.dev

Jaký je efektivní způsob implementace singletonového vzoru v Javě?

Jaký je efektivní způsob implementace singletonového vzoru v Javě?

752

Použít výčet:

public enum Foo {
    INSTANCE;
}

Joshua Bloch vysvětlil tento přístup ve svém Efektivní Java Reloaded talk na Google I/O 2008: odkaz na video . Viz také snímky 30-32 jeho prezentace ( effect_Java_reloaded.pdf ):

Správný způsob implementace serializovatelného Singletonu

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

Edit: An online část "Efektivní Java" říká: 

„Tento přístup je funkčně ekvivalentní přístupu veřejného terénu, s výjimkou toho, že je výstižnější, poskytuje strojům serializace bezplatně a poskytuje bezchybnou záruku proti vícenásobné instanci, dokonce i v případě sofistikovaných serializačních nebo reflexních útoků. přesto být široce adoptovaný, jeden-element enum typu je nejlepší způsob, jak realizovat singleton.

743
Stephen Denne

V závislosti na použití existuje několik "správných" odpovědí.

Vzhledem k tomu, Java5 je nejlepší způsob, jak to udělat, je použít enum:

public enum Foo {
   INSTANCE;
}

Pre Java5 je nejjednodušší případ:

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");
    }
}

Pojďme jít přes kód. Za prvé, chcete, aby byla třída konečná. V tomto případě jsem použil klíčové slovo final, aby uživatelé věděli, že je konečné. Pak je třeba, aby konstruktor byl soukromý, aby zabránil uživatelům vytvořit si vlastní Foo. Házení výjimky z konstruktoru zabraňuje uživatelům použít odraz k vytvoření druhého Foo. Potom vytvoříte pole private static final Foo, které bude obsahovat pouze instanci, a metodu public static Foo getInstance() ji vrátíte. Specifikace jazyka Java zajišťuje, že konstruktor se nazývá pouze při prvním použití třídy.

Pokud máte velmi velký objekt nebo těžký stavební kód A také máte jiné přístupné statické metody nebo pole, která mohou být použita před potřebou instance, pak a teprve potom musíte použít lazy inicializaci.

Pro načtení instance můžete použít private static class. Kód by pak vypadal takto:

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;
    }
}

Vzhledem k tomu, že řádek private static final Foo INSTANCE = new Foo(); je prováděn pouze tehdy, když je ve skutečnosti používána třída FooLoader, postará se o lazy instanci a je zaručeno, že bude bezpečný.

Pokud také chcete být schopni serializovat váš objekt, musíte se ujistit, že deserializace nebude vytvářet 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() zajistí, že bude vrácena pouze jediná instance, i když byl objekt serializován v předchozím běhu programu.

224
Roel Spilker

Disclaimer: Právě jsem shrnula všechny úžasné odpovědi a napsala jsem je podle mých slov.


Při realizaci Singleton máme 2 možnosti
1. Lazy zatížení
2. Včasné zatížení

Lazy načítání přidává trochu režie (spousta být upřímný), takže použít pouze tehdy, když máte velmi velký objekt nebo těžké stavební kód A také mají jiné přístupné statické metody nebo pole, které by mohly být použity před instanci je potřeba, pak a teprve pak je třeba použít lazy inicializace.Obdobně volba brzy načítání je dobrá volba.

Nejjednodušší způsob implementace Singletonu je 

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;
    }
}

Všechno je dobré, s výjimkou jeho brzy naloženého singletonu. Vyzkoušejte líný načtený singleton

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;
    }
}

Doposud tak dobrý, ale náš hrdina nepřežije, když bojuje sám s více zlými nitěmi, kteří chtějí mnoho instancí našeho hrdiny.

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 nestačí chránit hrdinu, opravdu! To je to nejlepší, co můžeme/měli udělat, abychom pomohli našemu hrdinovi 

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;
    }
}

Toto se nazývá "Dvojitá kontrola zamykání". Je snadné zapomenout na nestabilní prohlášení a těžko pochopit, proč je to nutné.
Podrobnosti: http://www.cs.umd.edu/~pugh/Java/memoryModel/DoubleCheckedLocking.html

Nyní jsme si jisti, co je zlé, ale co krutá serializace? Musíme se ujistit, že i když de-serialiaztion žádný nový objekt není vytvořen

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() zajistí, že bude vrácena jediná instance, i když byl objekt serializován v předchozím běhu našeho programu.

Nakonec jsme přidali dostatečnou ochranu proti vláknům a serializaci, ale náš kód vypadá objemně a ošklivě. Dejme našemu hrdinovi, aby se přestal

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;
    }
}

Ano, toto je náš stejný hrdina :)
Vzhledem k tomu, že řádek private static final Foo INSTANCE = new Foo(); je prováděn pouze tehdy, když je třída FooLoader skutečně použita, postará se o lazy instanci, 

a je zaručeno, že bude bezpečné.

A přišli jsme tak daleko, tady je nejlepší způsob, jak dosáhnout všeho, co jsme udělali, je nejlepší možný způsob 

 public enum Foo {
       INSTANCE;
   }

Které interně budou zpracovány jako 

public class Foo {

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

To už není strach ze serializace, nití a ošklivého kódu. Také ENUMS singleton jsou inicializovány lenochodně

Tento přístup je funkčně ekvivalentní přístupu veřejného terénu, s výjimkou toho, že je výstižnější, poskytuje serializační zařízení Zdarma a poskytuje nezpochybnitelnou záruku proti opakování tváří v tvář sofistikované serializaci nebo odrazovým útokům. Zatímco tento přístup má být ještě široce adoptovaný, jeden-element enum typu je nejlepší způsob, jak implementovat singleton.

-Joshua Bloch v „Efektivní Javě“ 

Nyní jste možná zjistili, proč jsou ENUMS považovány za nejlepší způsob, jak implementovat Singleton a díky za vaši trpělivost :)
Aktualizováno na mém blog

127
xyz

Řešení publikované společností Stu Thompson je platné v jazyce Java5.0 a novějším. Ale raději bych ho nepoužíval, protože si myslím, že je to chyba náchylná.

Je snadné zapomenout na nestabilní prohlášení a těžko pochopit, proč je to nutné. Bez volatile by tento kód již nebyl bezpečný pro závity kvůli dvojitě kontrolované blokovací antipattern. Více informací naleznete v odstavci 16.2.4 Concreteency in Practice . Stručně: Tento vzor (před Java5.0 nebo bez příkazu volatile) může vrátit odkaz na objekt Bar, který je (stále) v nesprávném stavu.

Tento vzor byl vynalezen pro optimalizaci výkonu. Ale to už opravdu není skutečný problém. Následující lazy inicializační kód je rychlý a mnohem důležitější - čitelnější.

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

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

Bezpečné vlákno v jazyce Java 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;
    }
}

EDIT: Zde věnujte pozornost modifikátoru volatile. :) Je důležité, protože bez něj JMM (Java Memory Model) nezaručuje další vlákna, aby viděla změny své hodnoty. Synchronizace nese postará o to - pouze serializuje přístup k tomuto bloku kódu.

EDIT 2 : @ Bnoova odpověď popisuje přístup doporučený Billem Pughem (FindBugs) a je hádatelnější. Jdi číst a hlasovat i jeho odpověď.

94
Stu Thompson

Zapomeňte lazy inicializace , je to příliš problematické. Toto je nejjednodušší řešení:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

Ujistěte se, že to opravdu potřebujete. Do google pro "singleton anti-pattern" vidět nějaké argumenty proti němu. Neexistuje nic, co je v zásadě špatné s tím myslím, ale je to jen mechanismus pro odhalení některých globálních zdrojů/dat, takže se ujistěte, že je to nejlepší způsob. Zejména jsem zjistil, závislost injekce užitečnější zejména, pokud jste také pomocí jednotkových testů, protože DI umožňuje použít zesměšňované zdroje pro účely testování.

47
Neil Burroughs

Nezapomeňte, že Singleton je pouze Singleton pro Classloader, který ho načetl. Pokud používáte více zavaděčů (Containers), každý má svou vlastní verzi Singletonu.

26
Javamann

Jsem mystifikován některými z odpovědí, které naznačují DI jako alternativu k používání singletonů; jedná se o nesouvisející pojmy. DI můžete použít k injekci instancí singleton nebo non-singleton (např. Per-thread). Přinejmenším je to pravda, pokud používáte Spring 2.x, nemůžu mluvit za jiné DI rámce.

Moje odpověď na OP by tedy byla (ve všech případech kromě triviálního vzorového kódu) na:

  1. Použijte pak rámec DI, jako je jaro
  2. Udělejte to součástí vaší konfigurace DI, zda vaše závislosti jsou singletons, scoped požadavku, relace scoped nebo cokoliv.

Tento přístup vám dává Nice decoupled (a tedy flexibilní a testovatelnou) architekturu, kde zda používat singleton je snadno reverzibilní detail implementace (za předpokladu, že všechny singletony, které používáte, jsou samozřejmostí).

21
Andrew Swan

Opravdu zvážit, proč potřebujete singleton před napsáním. Tam je kvazi-náboženská debata o jejich použití, které můžete docela snadno narazit, pokud jste google singletons v Javě.

Osobně se snažím vyhnout singletonům tak často, jak je to jen možné, z mnoha důvodů, z nichž většina může být nalezena v googling singletons. Mám pocit, že často jsou singletony zneužívány, protože jsou snadno pochopitelné každým, používají se jako mechanismus pro získání „globálních“ dat do designu OO a používají se, protože je snadné objekt obejít. řízení životního cyklu (nebo opravdu přemýšlet o tom, jak můžete provádět A zevnitř B). Podívejte se na věci jako Inverze řízení (IoC) nebo Dependency Injection (DI) pro Nice middleground.

Pokud opravdu potřebujete, pak wikipedia má dobrý příklad řádné implementace singletonu.

20
Aidos

Následují 3 různé přístupy

1) Enum

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

2) Dvojité zaškrtnutí Zamykání/Lazy

/**
* 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) Statická tovární metoda

/**
* 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;
    }
}
16
Abhijit Gaikwad

Používám Jarní rámec k řízení svých singletonů. To nevynucuje "singleton-ness" třídy (které nemůžete opravdu dělat, pokud existuje více tříd zavaděčů), ale poskytuje opravdu snadný způsob, jak vytvořit a konfigurovat různé továrny pro vytváření různých typů objektů.

13
Matt

Verze 1:

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

Lazy load, vlákno bezpečné s blokováním, nízký výkon z důvodu synchronized.

Verze 2:

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

Lazy zatížení, vlákno bezpečné s non-blokování, vysoký výkon.

11
coderz

Wikipedia má některé příklady singletonů, také v Javě. Implementace Java 5 vypadá docela kompletně a je bezpečná pro vlákna (dvojitě kontrolované uzamčení).

10
macbirdie

Pokud nepotřebujete líné načítání, pak zkuste

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();
    }
}

Chcete-li, aby byl váš singleton bezpečný a bezpečný, vyzkoušejte vzor dvojité kontroly 

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();
        }
}

Vzhledem k tomu, že dvojitý kontrolní vzor není zaručen pro práci (kvůli nějakému problému s kompilátory, o tom nevím nic víc.), Můžete také zkusit synchronizovat celou metodu getInstance nebo vytvořit registr pro všechny vaše Singletons. 

10
Aleksi Yrttiaho

Řekl bych Enum singleton 

Singleton pomocí enum v Javě je obecně způsob, jak deklarovat enum singleton. Enum singleton může obsahovat proměnnou instance a metodu instance. Pro jednoduchost si také všimněte, že pokud používáte jakoukoliv metodu instancí, než je potřeba zajistit bezpečnost této metody, pokud vůbec ovlivní stav objektu.

Použití enumu je velmi snadné implementovat a nemá žádné nevýhody, pokud jde o serializovatelné objekty, které musí být obcházeny jinými způsoby.

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

Můžete k němu přistupovat pomocí Singleton.INSTANCE, což je mnohem jednodušší než volání metody getInstance() na Singleton.

1.12 Serializace enumových konstant

Enumové konstanty jsou serializovány odlišně než běžné serializovatelné nebo externalizovatelné objekty. Serializovaná forma enum konstanty sestává výhradně z jeho jména; hodnoty pole konstanty nejsou ve formuláři přítomny. Chcete-li serializovat konstantu enum, ObjectOutputStream zapíše hodnotu vrácenou metodou názvu konstanty enum. Chcete-li deserializovat konstantu enum, ObjectInputStream přečte konstantní název z proudu; deserializovaná konstanta se pak získá voláním metody Java.lang.Enum.valueOf, předáním typu enum konstanty spolu s názvem přijaté konstanty jako argumenty. Stejně jako ostatní serializovatelné nebo externalizovatelné objekty mohou konstanty enum fungovat jako cíle zpětných odkazů, které se následně objeví v serializačním proudu.

Proces, kterým jsou konstanty enumu serializovány, nelze přizpůsobit: metody writeObject, readObject, readObjectNoData, writeReplace a readResolve specifické pro jednotlivé třídy definované typy enum jsou při serializaci a deserializaci ignorovány. Podobně jsou ignorována všechna deklarace serialPersistentFields nebo serialVersionUID - všechny typy enum mají pevnou serialVersionUID0L. Dokumentace serializovatelných polí a dat pro typy enum je zbytečná, protože v typu odeslaných dat neexistuje žádná variace.

Citováno z Oracle docs

Další problém s konvenční Singletons je, že jakmile implementujete Serializable rozhraní, které již nezůstanou Singleton, protože readObject() metoda vždy vrátí novou instanci, jako je konstruktor v Javě. Tomu se lze vyhnout použitím readResolve() a vyřazením nově vytvořené instance nahrazením singletonem uvedeným níže 

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

To může být ještě složitější, pokud vaše Singleton třída udržet stav, jak je třeba, aby byly přechodné, ale s v Enum Singleton, Serialization je zaručena JVM.


Dobré čtení

  1. Singleton vzor
  2. Enums, Singletons a Deserialization
  3. Dvojité zaškrtnutí a vzor Singleton
8
NullPoiиteя
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");
    }
}
7
Dheeraj Sachan

Mohlo by to být trochu pozdě na hru, ale kolem implementace singletonu je hodně nuance. Vzor držáku nelze použít v mnoha situacích. A IMO při použití volatile - měli byste také použít lokální proměnnou. Začněme na začátku a opakujeme problém. Uvidíte, co tím myslím.


První pokus může vypadat takto:

public class MySingleton {

     private static MySingleton INSTANCE;

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

        return INSTANCE;
    }
    ...
}

Zde máme třídu MySingleton, která má soukromého statického člena nazvaného INSTANCE a veřejnou statickou metodu nazvanou getInstance (). Při prvním volání getInstance () je člen INSTANCE null. Tok pak spadá do podmínky vytvoření a vytvoří novou instanci třídy MySingleton. Následující volání getInstance () zjistí, že proměnná INSTANCE je již nastavena, a proto nevytváří další instanci MySingleton. To zajišťuje, že existuje pouze jedna instance MySingleton, která je sdílena všemi volajícími getInstance ().

Ale tato implementace má problém. Vícevláknové aplikace budou mít na vytvoření jediné instance podmínku rasy. Pokud více podprocesů provedení narazí na metodu getInstance () ve stejnou dobu (nebo kolem ní), každý z nich uvidí člena INSTANCE jako null. Výsledkem bude, že každý podproces vytvoří novou instanci MySingleton a následně nastaví člen INSTANCE.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Zde jsme použili synchronizované klíčové slovo v podpisu metody k synchronizaci metody getInstance (). To určitě vyřeší naše podmínky závodu. Vlákna budou nyní blokovat a postupně zadávat metodu. Vytváří však také problém s výkonností. Tato implementace nejen synchronizuje vytvoření jediné instance, ale synchronizuje všechny hovory do getInstance () včetně čtení. Čtení nemusí být synchronizováno, protože jednoduše vrátí hodnotu INSTANCE. Vzhledem k tomu, že čtení bude tvořit většinu našich hovorů (nezapomeňte, že u prvního volání dojde pouze k instanci), vznikne zbytečný zásah ze synchronizace celé metody.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Zde jsme přesunuli synchronizaci z podpisu metody na synchronizovaný blok, který zabalí vytvoření instance MySingleton. Ale vyřeší to náš problém? No, my už nejsme blokování čtení, ale také jsme udělali krok zpět. Více podprocesů narazí na metodu getInstance () ve stejnou dobu nebo kolem ní a všichni uvidí člena INSTANCE jako null. Následně zasáhnou synchronizovaný blok, kde se získá zámek a vytvoří se instance. Když tento podproces opustí blok, ostatní vlákna se budou zabývat zámkem a každý po druhém vlákno propadne blokem a vytvoří novou instanci naší třídy. Takže jsme hned tam, kde jsme začali.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Zde vydáváme další kontrolu od INSIDE bloku. Pokud již byl člen INSTANCE nastaven, budeme přeskočit inicializaci. Toto se nazývá zamykání s dvojitou kontrolou.

To řeší náš problém vícenásobného instalování. Ale opět naše řešení představuje další výzvu. Ostatní vlákna nemusí „vidět“, že člen INSTANCE byl aktualizován. Je to způsobeno tím, jak Java optimalizuje operace paměti. Vlákna kopírují původní hodnoty proměnných z hlavní paměti do mezipaměti CPU. Změny hodnot jsou pak zapisovány do této mezipaměti a čteny z ní. Jedná se o funkci jazyka Java, která má optimalizovat výkon. To však vytváří problém pro naši implementaci singletonu. Druhý podproces - zpracovávaný jiným CPU nebo jádrem, s použitím jiné cache - neuvidí změny provedené prvním. To způsobí, že druhý podproces uvidí člena INSTANCE jako null, čímž se vytvoří nová instance našeho singletonu.


private static volatile MySingleton INSTANCE;

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

    return INSTANCE;
}

Řešíme to pomocí volatilního klíčového slova na prohlášení člena INSTANCE. To řekne kompilátoru, aby vždy četl a zapisoval do hlavní paměti a ne do mezipaměti CPU.

Ale tato jednoduchá změna přichází za cenu. Vzhledem k tomu, že obcházíme mezipaměť CPU, budeme pokaždé, když budeme pracovat s volatilním členem INSTANCE, provést výkonový zásah, který děláme 4krát. Ověříme existenci (1 a 2), nastavíme hodnotu (3) a vrátíme hodnotu (4). Dalo by se argumentovat, že tato cesta je okrajovým případem, protože instanci vytváříme pouze během prvního volání metody. Snadný výkon na vytvoření je přijatelný. Ale i náš hlavní případ použití bude fungovat na těkavém členu dvakrát. Jednou zkontrolovat existenci a znovu vrátit jeho hodnotu.


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; }

Nedávno jsem o tom napsal článek. Deconstructing Singleton . Více informací o těchto příkladech a příkladech "držáku" najdete zde. Existuje také příklad reálného světa, který ukazuje dvojitý kontrolovaný volatilní přístup. Snad to pomůže.

.

6
Michael Andrews

Takto implementujete jednoduchý 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;
    }
}

Toto je způsob, jak správně vytvořit líný soubor 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();
    }
}
4
Nicolas Filotto

Pokud potřebujete načíst instanci proměnné třídy lazily, potřebujete double-checking idiom. Pokud potřebujete načíst statickou proměnnou nebo singleton lenoše, potřebujete inicializaci na požádání držitele idiom. 

Kromě toho, pokud singleton musí být seriliazble, musí být všechna ostatní pole přechodná a metoda readResolve () musí být implementována, aby se zachoval invariant objektu singleton. V opačném případě bude pokaždé, když je objekt deserializován, vytvořena nová instance objektu. Co readResolve () dělá, je nahradit nový objekt čtený readObject (), který nutil tento nový objekt, aby byl shromážděný, protože neexistuje žádná proměnná, která by na něj odkazovala.

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

Různé způsoby vytvoření objektu singleton:

  1. Podle Joshua Blocha - Enum by byl nejlepší.

  2. můžete také použít dvojité zamykání.

  3. Lze použít i vnitřní statickou třídu.

3
Shailendra Singh

Enum singleton

Nejjednodušší způsob, jak implementovat Singleton, který je bezpečný pro vlákna, je pomocí Enum

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

Tento kód funguje od zavedení Enum v jazyce Java 1.5

Dvojité zaškrtnutí

Pokud chcete kódovat „klasický“ singleton, který pracuje v prostředí s více podprocesy (počínaje jazykem Java 1.5), měli byste tento použít.

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 ;
  }
}

To není bezpečné vlákno před 1.5, protože implementace volatile klíčové slovo bylo jiné.

Brzy načtení Singleton (funguje i před Java 1.5)

Tato implementace vytvoří instanci singleton při načtení třídy a zajistí bezpečnost podprocesů.

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");
  }

}
3
Dan Moldovan

Pro JSE 5.0 a vyšší používejte přístup Enum, jinak používejte statický přístup držáku singleton ((líný způsob načítání popsaný Billem Pughem), pozdější řešení je také bezpečné, bez nutnosti speciálních jazykových konstrukcí (tj. Nestálých nebo synchronizovaných).

2
raoadnan

Dalším argumentem často používaným proti Singletons jsou jejich problémy s testovatelností. Singletony nejsou snadno testovatelné pro účely testování. Pokud se ukáže, že se jedná o problém, rád bych provedl následující drobné úpravy:

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");
    }
}

Přidaná metoda setInstance umožňuje během testování nastavit implementaci makety třídy singleton:

public class SingletonMock extends SingletonImpl {

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

}

To také funguje s časnými inicializačními přístupy:

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");
    }

}

To má tu nevýhodu, že je tato funkce vystavena také normální aplikaci. Jiní vývojáři pracující na tomto kódu by mohli být v pokušení použít metodu ´setInstance´ ke změně pozměněné funkce a tím ke změně chování celé aplikace, proto by tato metoda měla obsahovat alespoň dobré varování v javadoc.

Nicméně pro možnost testování makety (v případě potřeby) může být expozice kódu přijatelnou cenou.

2
user3792852

nejjednodušší třída singletonů

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

Stále si myslím, že po Java 1.5, enum je nejlepší dostupná singleton implementace k dispozici, protože také zajišťuje, že i v prostředí s více vlákny - je vytvořena pouze jedna instance.

public enum Singleton{ INSTANCE; }

a jste hotovi !!!

0
shikjohari

Podívejte se na tento příspěvek.

Příklady návrhových vzorů GoF v základních knihovnách Java

Od sekce "Singleton" nejlepší odpovědi,

Singleton (rozpoznatelný podle metod tvorby, které vracejí stejnou instanci (obvykle sám))

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

Můžete se také dozvědět příklad Singletonu ze samotných Java tříd.

0
phi

Nejlepší vzor singleton, jaký jsem kdy viděl, používá rozhraní Dodavatele.

  • Je to obecné a opakovaně použitelné
  • Podporuje lazy inicializaci
  • Je synchronizován pouze do té doby, než byl inicializován, potom je blokující dodavatel nahrazen neblokujícím dodavatelem.

Viz. níže:

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();
    }
}
0
user1024314