Domanda

Qual è un modo efficiente per implementare un modello singleton in Java?

È stato utile?

Soluzione

Utilizza un'enumerazione:

public enum Foo {
    INSTANCE;
}

Joshua Bloch ha spiegato questo approccio nel suo Java efficace ricaricato parlare al Google I/O 2008: collegamento al video.Vedi anche le diapositive 30-32 della sua presentazione (efficace_java_reloaded.pdf):

Il modo giusto per implementare un singleton serializzabile

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

Modificare: UN porzione online di "Effective Java" dice:

"Questo approccio è funzionalmente equivalente all'approccio sul campo pubblico, tranne per il fatto che è più conciso, fornisce gratuitamente il meccanismo di serializzazione e fornisce una garanzia ferrea contro le istanze multiple, anche a fronte di sofisticati attacchi di serializzazione o di riflessione.Sebbene questo approccio non sia ancora stato ampiamente adottato, un tipo enum a elemento singolo è il modo migliore per implementare un singleton."

Altri suggerimenti

A seconda dell'utilizzo ci sono diverse risposte "corrette".

Da Java5 il modo migliore per farlo è usare un enum:

public enum Foo {
   INSTANCE;
}

Prima di Java5, il caso più semplice è:

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

Esaminiamo il codice.Innanzitutto, vuoi che la lezione sia definitiva.In questo caso, ho usato il file final parola chiave per far sapere agli utenti che è definitiva.Quindi è necessario rendere privato il costruttore per impedire agli utenti di creare il proprio Foo.Lanciare un'eccezione dal costruttore impedisce agli utenti di utilizzare la riflessione per creare un secondo Foo.Quindi crei un file private static final Foo campo per contenere l'unica istanza e a public static Foo getInstance() metodo per restituirlo.La specifica Java garantisce che il costruttore venga chiamato solo quando la classe viene utilizzata per la prima volta.

Quando si dispone di un oggetto molto grande o di un codice di costruzione pesante E si hanno anche altri metodi o campi statici accessibili che potrebbero essere utilizzati prima che sia necessaria un'istanza, allora e solo allora è necessario utilizzare l'inizializzazione lenta.

Puoi usare a private static class per caricare l'istanza.Il codice sarebbe quindi simile a:

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

Dalla linea private static final Foo INSTANCE = new Foo(); viene eseguito solo quando la classe FooLoader viene effettivamente utilizzata, questo si occupa dell'istanziazione lenta ed è garantito che sia thread-safe.

Quando vuoi anche essere in grado di serializzare il tuo oggetto devi assicurarti che la deserializzazione non crei una copia.

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

Il metodo readResolve() si assicurerà che venga restituita l'unica istanza, anche quando l'oggetto è stato serializzato in un'esecuzione precedente del programma.

Disclaimer: Ho appena riassunto tutte le fantastiche risposte e le ho scritte con parole mie.


Durante l'implementazione di Singleton abbiamo 2 opzioni
1.Caricamento pigro
2.Caricamento anticipato

Il caricamento lento aggiunge un po' di sovraccarico (molto a dire il vero), quindi usalo solo quando hai un oggetto molto grande o un codice di costruzione pesante E hai anche altri metodi o campi statici accessibili che potrebbero essere utilizzati prima che sia necessaria un'istanza, allora e solo allora è necessario utilizzare l'inizializzazione lenta. Altrimenti la scelta del caricamento anticipato è una buona scelta.

Il modo più semplice per implementare Singleton è

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

Va tutto bene tranne il singleton caricato in anticipo.Proviamo il singleton caricato pigro

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

Fin qui tutto bene, ma il nostro eroe non sopravviverà mentre combatte da solo con molteplici thread malvagi che vogliono moltissime istanze del nostro eroe.Quindi proteggiamolo dal malvagio multi threading

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

ma non basta proteggere il nostro eroe, Davvero!!!Questo è il meglio che possiamo/dovremmo fare per aiutare il nostro eroe

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

Questo è chiamato "idioma di bloccaggio a doppio controllo".È facile dimenticare l'affermazione volatile ed è difficile capire perché sia ​​necessaria.
Per dettagli : http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Ora siamo sicuri del filo malvagio, ma che dire della crudele serializzazione?Dobbiamo assicurarci che anche durante la deserializzazione non venga creato alcun nuovo oggetto

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

Il metodo readResolve() si assicurerà che venga restituita l'unica istanza, anche quando l'oggetto è stato serializzato in un'esecuzione precedente del nostro programma.

Finalmente abbiamo aggiunto una protezione sufficiente contro thread e serializzazione, ma il nostro codice appare voluminoso e brutto.Diamo una nuova veste al nostro eroe

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

Sì, questo è il nostro stesso eroe :)
Dalla linea private static final Foo INSTANCE = new Foo(); viene eseguito solo quando la classe FooLoader viene effettivamente utilizzato, questo si occupa dell'istanziazione pigra,

ed è garantito che sia thread-safe.

E siamo arrivati ​​​​fin qui, ecco il modo migliore per realizzare tutto ciò che abbiamo fatto nel miglior modo possibile

 public enum Foo {
       INSTANCE;
   }

Che internamente verrà trattato come

public class Foo {

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

Niente più paura della serializzazione, dei thread e del codice brutto.Anche Gli ENUMS singleton vengono inizializzati pigramente.

Questo approccio è funzionalmente equivalente all'approccio sul campo pubblico, tranne per il fatto che è più conciso, fornisce i macchinari di serializzazione gratuitamente e fornisce una garanzia di ferro contro l'istanziazione multipla, anche di fronte a sofisticati attacchi di serializzazione o riflessione.Sebbene questo approccio non sia ancora stato ampiamente adottato, un tipo ENUM a elemento singolo è il modo migliore per implementare un singleton.

-Joshua Bloch in "Java efficace"

Ora potresti aver capito perché gli ENUMS sono considerati il ​​modo migliore per implementare Singleton e grazie per la pazienza :)
Aggiornato sul mio blog.

La soluzione pubblicata da Stu Thompson è valida in Java5.0 e versioni successive.Ma preferirei non usarlo perché penso che sia soggetto a errori.

È facile dimenticare l'affermazione volatile ed è difficile capire perché sia ​​necessaria.Senza il volatile questo codice non sarebbe più thread-safe a causa dell'antipattern di blocco a doppio controllo.Si veda di più al riguardo nel paragrafo 16.2.4 del Concorrenza Java in pratica.In breve:Questo modello (prima di Java5.0 o senza l'istruzione volatile) potrebbe restituire un riferimento all'oggetto Bar che è (ancora) in uno stato errato.

Questo modello è stato inventato per l'ottimizzazione delle prestazioni.Ma questa non è più una vera preoccupazione.Il seguente codice di inizializzazione pigro è veloce e, cosa più importante, più facile da leggere.

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

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

Thread-safe in 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;
    }
}

MODIFICARE:Presta attenzione a volatile modificatore qui.:) È importante perché senza di esso, gli altri thread non sono garantiti dal JMM (Java Memory Model) per vedere le modifiche al suo valore.La sincronizzazione non prenditene cura: serializza solo l'accesso a quel blocco di codice.

MODIFICA 2:La risposta di @Bno descrive in dettaglio l'approccio raccomandato da Bill Pugh (FindBugs) ed è discutibile migliore.Andate a leggere e votate anche la sua risposta.

Dimenticare inizializzazione pigra, è troppo problematico.Questa è la soluzione più semplice:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

Assicurati di averne davvero bisogno.Fai una ricerca su Google per "anti-pattern singleton" per vedere alcuni argomenti contro di esso.Suppongo che non ci sia nulla di intrinsecamente sbagliato in questo, ma è solo un meccanismo per esporre alcune risorse/dati globali, quindi assicurati che questo sia il modo migliore.In particolare, ho trovato l'inserimento delle dipendenze più utile soprattutto se si utilizzano anche test unitari perché DI consente di utilizzare risorse derise a scopo di test.

Sono sconcertato da alcune delle risposte che suggeriscono DI come alternativa all'uso dei singleton;questi sono concetti non correlati.È possibile utilizzare DI per iniettare singleton o non singleton (ad es.per thread) istanze.Almeno questo è vero se usi Spring 2.x, non posso parlare per altri framework DI.

Quindi la mia risposta all'OP sarebbe (in tutto tranne il codice di esempio più banale) a:

  1. Utilizza quindi un framework DI come Spring
  2. Rendilo parte della tua configurazione DI indipendentemente dal fatto che le tue dipendenze siano singleton, con ambito richiesta, con ambito sessione o altro.

Questo approccio offre una bella architettura disaccoppiata (e quindi flessibile e testabile) in cui se utilizzare un singleton è un dettaglio di implementazione facilmente reversibile (a condizione che tutti i singleton utilizzati siano thread-safe, ovviamente).

Considera davvero il motivo per cui hai bisogno di un singleton prima di scriverlo.C'è un dibattito quasi religioso sul loro utilizzo in cui puoi inciampare abbastanza facilmente se cerchi singleton su Google in Java.

Personalmente cerco di evitare i single il più spesso possibile per molte ragioni, la maggior parte delle quali possono essere trovate cercando su Google i single.Ritengo che molto spesso i singleton vengano abusati perché sono facili da comprendere da tutti, vengono utilizzati come meccanismo per inserire dati "globali" in una progettazione OO e vengono utilizzati perché è facile aggirare la gestione del ciclo di vita degli oggetti (o pensando davvero a come puoi fare A dall'interno di B).Guarda cose come Inversion of Control (IoC) o Dependency Injection (DI) per una buona via di mezzo.

Se ne hai davvero bisogno, Wikipedia ha un buon esempio di corretta implementazione di un singleton.

Di seguito sono riportati 3 approcci diversi

1) Enum

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

2) Ricontrolla il blocco/caricamento lento

/**
* 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) Metodo della fabbrica statica

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

Utilizzo Spring Framework per gestire i miei singleton.Non impone la "singleton-ness" della classe (cosa che non puoi comunque fare se sono coinvolti più caricatori di classi) ma fornisce un modo davvero semplice per costruire e configurare diverse fabbriche per la creazione di diversi tipi di oggetti.

Versione 1:

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

Caricamento lento, thread-safe con blocco, prestazioni ridotte a causa di synchronized.

Versione 2:

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

Caricamento lento, thread-safe con prestazioni non bloccanti e elevate.

Wikipedia ne ha alcuni esempi di singleton, anche in Java.L'implementazione di Java 5 sembra piuttosto completa ed è thread-safe (è applicato un doppio controllo del blocco).

Se non hai bisogno del caricamento lento, prova semplicemente

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

Se vuoi un caricamento lento e vuoi che il tuo Singleton sia thread-safe, prova il modello di doppio controllo

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

Dato che il modello di doppio controllo non è garantito che funzioni (a causa di qualche problema con i compilatori, non ne so niente di più), potresti anche provare a sincronizzare l'intero metodo getInstance o creare un registro per tutti i tuoi Singleton.

Direi Enum singleton

Singleton che utilizza enum in Java è generalmente un modo per dichiarare enum singleton.L'enum singleton può contenere una variabile di istanza e un metodo di istanza.Per semplicità, tieni presente anche che se stai utilizzando un metodo di istanza, è necessario garantire la sicurezza del thread di quel metodo, qualora influisca sullo stato dell'oggetto.

L'utilizzo di un'enumerazione è molto semplice da implementare e non presenta inconvenienti per quanto riguarda gli oggetti serializzabili, che devono essere aggirati in altri modi.

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

Puoi accedervi tramite Singleton.INSTANCE, molto più semplice che chiamare getInstance() metodo su Singleton.

1.12 Serializzazione delle costanti Enum

Le costanti enum vengono serializzate in modo diverso rispetto ai normali oggetti serializzabili o esternalizzabili.La forma serializzata di una costante enum consiste esclusivamente nel suo nome;i valori dei campi della costante non sono presenti nel modulo.Per serializzare una costante enum, ObjectOutputStream scrive il valore restituito dal metodo name della costante enum.Per deserializzare una costante enum, ObjectInputStream legge il nome della costante dallo stream;la costante deserializzata si ottiene quindi chiamando the java.lang.Enum.valueOf metodo, passando il tipo enum della costante insieme al nome della costante ricevuta come argomenti.Come altri oggetti serializzabili o esternalizzabili, le costanti enum possono funzionare come destinazioni dei riferimenti all'indietro che appaiono successivamente nel flusso di serializzazione.

Il processo mediante il quale vengono serializzate le costanti enum non può essere personalizzato:qualsiasi specifico della classe writeObject, readObject, readObjectNoData, writeReplace, E readResolve i metodi definiti dai tipi enum vengono ignorati durante la serializzazione e la deserializzazione.Allo stesso modo, qualsiasi serialPersistentFields O serialVersionUID anche le dichiarazioni di campo vengono ignorate: tutti i tipi di enumerazione hanno un valore fisso serialVersionUID Di 0L.La documentazione dei campi e dei dati serializzabili per i tipi enum non è necessaria, poiché non vi è alcuna variazione nel tipo di dati inviati.

Citato dai documenti Oracle

Un altro problema con i Singleton convenzionali è che una volta implementati Serializable interfaccia, non rimangono più Singleton perché readObject() Il metodo restituisce sempre una nuova istanza come il costruttore in Java.Questo può essere evitato utilizzando readResolve() e scartando l'istanza appena creata sostituendola con singleton come di seguito

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

Questo può diventare ancora più complesso se la tua classe Singleton mantiene lo stato, poiché è necessario renderli transitori, ma con Enum Singleton, la serializzazione è garantita da JVM.


Buona lettura

  1. Modello Singleton
  2. Enumerazioni, Singleton e Deserializzazione
  3. Chiusura a doppio controllo e modello Singleton

Potrebbe essere un po' tardi per questo, ma ci sono molte sfumature nell'implementazione di un singleton.Il modello di supporto non può essere utilizzato in molte situazioni.E IMO quando usi un volatile, dovresti usare anche una variabile locale.Cominciamo dall'inizio e iteriamo sul problema.Vedrai cosa intendo.


Il primo tentativo potrebbe assomigliare a questo:

public class MySingleton {

     private static MySingleton INSTANCE;

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

        return INSTANCE;
    }
    ...
}

Qui abbiamo la classe MySingleton che ha un membro statico privato chiamato INSTANCE e un metodo statico pubblico chiamato getInstance().La prima volta che viene chiamato getInstance(), il membro INSTANCE è null.Il flusso rientrerà quindi nella condizione di creazione e creerà una nuova istanza della classe MySingleton.Le chiamate successive a getInstance() scopriranno che la variabile INSTANCE è già impostata e pertanto non creeranno un'altra istanza di MySingleton.Ciò garantisce che ci sia una sola istanza di MySingleton condivisa tra tutti i chiamanti di getInstance().

Ma questa implementazione presenta un problema.Le applicazioni multi-thread avranno una condizione di competizione sulla creazione della singola istanza.Se più thread di esecuzione raggiungono il metodo getInstance() contemporaneamente (o intorno a esso), ciascuno di essi vedrà il membro INSTANCE come null.Ciò comporterà che ogni thread crei una nuova istanza MySingleton e successivamente imposti il ​​membro INSTANCE.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Qui abbiamo utilizzato la parola chiave sincronizzata nella firma del metodo per sincronizzare il metodo getInstance().Questo risolverà sicuramente la nostra condizione di gara.I thread ora si bloccheranno e accederanno al metodo uno alla volta.Ma crea anche un problema di prestazioni.Questa implementazione non solo sincronizza la creazione della singola istanza, ma sincronizza anche tutte le chiamate a getInstance(), comprese le letture.Le letture non necessitano di essere sincronizzate poiché restituiscono semplicemente il valore di INSTANCE.Poiché le letture costituiranno la maggior parte delle nostre chiamate (ricorda, l'istanziazione avviene solo alla prima chiamata), incorreremo in un inutile calo delle prestazioni sincronizzando l'intero metodo.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Qui abbiamo spostato la sincronizzazione dalla firma del metodo a un blocco sincronizzato che avvolge la creazione dell'istanza MySingleton.Ma questo risolve il nostro problema?Bene, non stiamo più bloccando le letture, ma abbiamo anche fatto un passo indietro.Più thread utilizzeranno il metodo getInstance() nello stesso momento o all'incirca nello stesso momento e vedranno tutti il ​​membro INSTANCE come null.Quindi colpiranno il blocco sincronizzato dove si otterrà il blocco e si creerà l'istanza.Quando quel thread esce dal blocco, gli altri thread si contenderanno il lock, e uno dopo l'altro ogni thread passerà attraverso il blocco e creerà una nuova istanza della nostra classe.Quindi siamo tornati esattamente al punto di partenza.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Qui emettiamo un altro assegno dall'INTERNO del blocco.Se il membro INSTANCE è già stato impostato, salteremo l'inizializzazione.Questo si chiama bloccaggio a doppio controllo.

Questo risolve il nostro problema di istanziazioni multiple.Ma ancora una volta, la nostra soluzione ha presentato un’altra sfida.Altri thread potrebbero non "vedere" che il membro INSTANCE è stato aggiornato.Ciò è dovuto al modo in cui Java ottimizza le operazioni di memoria.I thread copiano i valori originali delle variabili dalla memoria principale nella cache della CPU.Le modifiche ai valori vengono quindi scritte e lette da tale cache.Questa è una funzionalità di Java progettata per ottimizzare le prestazioni.Ma questo crea un problema per la nostra implementazione singleton.Un secondo thread — in fase di elaborazione da una CPU o core diverso, utilizzando una cache diversa — non vedrà le modifiche apportate dal primo.Ciò farà sì che il secondo thread veda il membro INSTANCE come null forzando la creazione di una nuova istanza del nostro singleton.


private static volatile MySingleton INSTANCE;

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

    return INSTANCE;
}

Risolviamo questo problema utilizzando la parola chiave volatile nella dichiarazione del membro INSTANCE.Ciò dirà al compilatore di leggere e scrivere sempre nella memoria principale e non nella cache della CPU.

Ma questo semplice cambiamento ha un costo.Poiché stiamo bypassando la cache della CPU, subiremo un calo di prestazioni ogni volta che operiamo sul membro volatile INSTANCE — cosa che facciamo 4 volte.Controlliamo due volte l'esistenza (1 e 2), impostiamo il valore (3) e quindi restituiamo il valore (4).Si potrebbe sostenere che questo percorso sia un caso marginale poiché creiamo l'istanza solo durante la prima chiamata del metodo.Forse un calo prestazionale nella creazione è tollerabile.Ma anche il nostro caso d'uso principale, si legge, opererà due volte sul membro volatile.Una volta per verificarne l'esistenza e un'altra per restituirne il valore.


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

Poiché il calo delle prestazioni è dovuto all'operare direttamente sul membro volatile, impostiamo una variabile locale sul valore del volatile e operiamo invece sulla variabile locale.Ciò ridurrà il numero di volte in cui operiamo sul volatile, recuperando così parte della nostra performance perduta.Nota che dobbiamo impostare nuovamente la nostra variabile locale quando entriamo nel blocco sincronizzato.Ciò garantisce che sia aggiornato con eventuali modifiche avvenute mentre aspettavamo il blocco.

Ho scritto un articolo su questo recentemente. Decostruire il Singleton.Puoi trovare maggiori informazioni su questi esempi e un esempio del modello "supporto" lì.C'è anche un esempio reale che mostra l'approccio volatile a doppio controllo.Spero che questo ti aiuti.

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

Ecco come implementare un semplice 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;
    }
}

Ecco come creare correttamente il tuo pigro 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();
    }
}

Hai bisogno doppio controllo idioma se è necessario caricare pigramente la variabile di istanza di una classe.Se è necessario caricare pigramente una variabile statica o un singleton, è necessario inizializzazione su richiesta del titolare idioma.

Inoltre, se il singleton deve essere serializzabile, tutti gli altri campi devono essere transitori e il metodo readResolve() deve essere implementato per mantenere invariante l'oggetto singleton.Altrimenti, ogni volta che l'oggetto viene deserializzato, verrà creata una nuova istanza dell'oggetto.Ciò che fa readResolve() è sostituire il nuovo oggetto letto da readObject(), che ha forzato la raccolta dei rifiuti per quel nuovo oggetto poiché non vi è alcuna variabile che faccia riferimento ad esso.

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

Vari modi per creare oggetti singleton:

  1. Secondo Joshua Bloch, Enum sarebbe il migliore.

  2. è possibile utilizzare anche il doppio controllo del blocco.

  3. È possibile utilizzare anche la classe statica interna.

Enum singleton

Il modo più semplice per implementare un Singleton thread-safe è utilizzare un Enum

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

Questo codice funziona dall'introduzione di Enum in Java 1.5

Chiusura ricontrollata

Se vuoi codificare un singleton “classico” che funzioni in un ambiente multithread (a partire da Java 1.5) dovresti usare questo.

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

Questo non è thread-safe prima della 1.5 perché l'implementazione della parola chiave volatile era diversa.

Caricamento anticipato di Singleton (funziona anche prima di Java 1.5)

Questa implementazione crea un'istanza del singleton quando la classe viene caricata e fornisce la sicurezza del thread.

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

}

Per JSE 5.0 e versioni successive adottare l'approccio Enum, altrimenti utilizzare l'approccio statico del supporto singleton ((un approccio di caricamento lento descritto da Bill Pugh).Quest'ultima soluzione è anche thread-safe senza richiedere costrutti linguistici speciali (ad es.volatile o sincronizzato).

Un altro argomento spesso usato contro i Singleton sono i loro problemi di testabilità.I singleton non sono facilmente derisi a scopo di test.Se questo risulta essere un problema, mi piace apportare la seguente leggera modifica:

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

L'aggiunto setInstance Il metodo consente di impostare un'implementazione mockup della classe singleton durante il test:

public class SingletonMock extends SingletonImpl {

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

}

Funziona anche con approcci di inizializzazione anticipata:

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

}

Ciò ha lo svantaggio di esporre questa funzionalità anche alla normale applicazione.Altri sviluppatori che lavorano su quel codice potrebbero essere tentati di utilizzare il metodo "setInstance" per alterare una funzione specifica e quindi cambiare il comportamento dell'intera applicazione, quindi questo metodo dovrebbe contenere almeno un buon avvertimento nel suo javadoc.

Tuttavia, per la possibilità di test di simulazione (quando necessario), questa esposizione del codice potrebbe essere un prezzo accettabile da pagare.

classe singleton più semplice

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

Penso ancora che dopo Java 1.5, enum sia la migliore implementazione singleton disponibile in quanto garantisce anche che anche negli ambienti multi thread venga creata una sola istanza.

public enum Singleton{ INSTANCE; }

e il gioco è fatto!!!

Dai un'occhiata a questo post

Esempi di modelli di progettazione GoF nelle librerie principali di Java

Dalla sezione "Singleton" della risposta migliore,

Singleton (riconoscibile dai metodi creazionali che restituiscono ogni volta la stessa istanza (solitamente di se stesso))

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

Puoi anche imparare l'esempio di Singleton dalle classi native Java stesse.

Il miglior modello singleton che abbia mai visto utilizza l'interfaccia Fornitore.

  • È generico e riutilizzabile
  • Supporta l'inizializzazione pigra
  • Viene sincronizzato solo finché non viene inizializzato, dopodiché il fornitore bloccante viene sostituito con un fornitore non bloccante.

Vedi sotto:

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

A volte un semplice "static Foo foo = new Foo();" non è abbastanza.Pensa solo ad alcuni inserimenti di dati di base che vuoi fare.

D'altra parte dovresti sincronizzare qualsiasi metodo che istanzia la variabile singleton in quanto tale.La sincronizzazione non è male di per sé, ma può portare a problemi di prestazioni o blocchi (in situazioni molto molto rare utilizzando questo esempio.La soluzione è

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

}

Ora cosa succede?La classe viene caricata tramite il caricatore di classi.Subito dopo che la classe è stata interpretata da un array di byte, la VM esegue il file statico { } - bloccare.questo è tutto il segreto:Il blocco statico viene chiamato solo una volta, nel momento in cui la classe (nome) specificata del pacchetto specificato viene caricata da questo caricatore di classi.

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;

    }

}

Poiché abbiamo aggiunto la parola chiave Synchronized prima di getInstance, abbiamo evitato la race condition nel caso in cui due thread chiami getInstance contemporaneamente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top