Singletons in Java

1. Introduzione

In questo rapido articolo, discuteremo i due modi più popolari di implementare Singleton in Java semplice.

2. Singleton basato sulla classe

L'approccio più popolare consiste nell'implementare un Singleton creando una classe regolare e assicurandosi che abbia:

  • Un costruttore privato
  • Un campo statico contenente la sua unica istanza
  • Un metodo factory statico per ottenere l'istanza

Aggiungeremo anche una proprietà delle informazioni, solo per un utilizzo successivo. Quindi, la nostra implementazione sarà simile a questa:

public final class ClassSingleton { private static ClassSingleton INSTANCE; private String info = "Initial info class"; private ClassSingleton() { } public static ClassSingleton getInstance() { if(INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; } // getters and setters }

Sebbene questo sia un approccio comune, è importante notare che può essere problematico negli scenari di multithreading , che è il motivo principale per l'utilizzo di Singletons.

In poche parole, può risultare in più di un'istanza, infrangendo il principio fondamentale del modello. Sebbene esistano soluzioni di blocco a questo problema, il nostro prossimo approccio risolve questi problemi a livello di radice.

3. Enum Singleton

Andando avanti, non discutiamo di un altro approccio interessante, che è quello di utilizzare le enumerazioni:

public enum EnumSingleton { INSTANCE("Initial class info"); private String info; private EnumSingleton(String info) { this.info = info; } public EnumSingleton getInstance() { return INSTANCE; } // getters and setters }

Questo approccio ha serializzazione e thread-safety garantiti dall'implementazione di enum stessa, che assicura internamente che sia disponibile solo la singola istanza, correggendo i problemi evidenziati nell'implementazione basata su classi.

4. Utilizzo

Per utilizzare il nostro ClassSingleton , dobbiamo semplicemente ottenere l'istanza staticamente:

ClassSingleton classSingleton1 = ClassSingleton.getInstance(); System.out.println(classSingleton1.getInfo()); //Initial class info ClassSingleton classSingleton2 = ClassSingleton.getInstance(); classSingleton2.setInfo("New class info"); System.out.println(classSingleton1.getInfo()); //New class info System.out.println(classSingleton2.getInfo()); //New class info

Per quanto riguarda EnumSingleton , possiamo usarlo come qualsiasi altro Java Enum:

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance(); System.out.println(enumSingleton1.getInfo()); //Initial enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance(); enumSingleton2.setInfo("New enum info"); System.out.println(enumSingleton1.getInfo()); // New enum info System.out.println(enumSingleton2.getInfo()); // New enum info

5. Errori comuni

Singleton è uno schema di progettazione apparentemente semplice e ci sono pochi errori comuni che un programmatore potrebbe commettere durante la creazione di un singleton.

Distinguiamo due tipi di problemi con i singleton:

  • esistenziale (abbiamo bisogno di un singleton?)
  • implementativo (lo implementiamo correttamente?)

5.1. Problemi esistenziali

Concettualmente, un singleton è una sorta di variabile globale. In generale, sappiamo che le variabili globali dovrebbero essere evitate, specialmente se i loro stati sono mutabili.

Non stiamo dicendo che non dovremmo mai usare i singleton. Tuttavia, stiamo dicendo che potrebbero esserci modi più efficienti per organizzare il nostro codice.

Se l'implementazione di un metodo dipende da un oggetto singleton, perché non passarlo come parametro? In questo caso, mostriamo esplicitamente da cosa dipende il metodo. Di conseguenza, possiamo facilmente deridere queste dipendenze (se necessario) durante l'esecuzione dei test.

Ad esempio, i singleton vengono spesso utilizzati per includere i dati di configurazione dell'applicazione (ad esempio, la connessione al repository). Se vengono utilizzati come oggetti globali, diventa difficile scegliere la configurazione per l'ambiente di test.

Pertanto, quando eseguiamo i test, il database di produzione viene rovinato con i dati del test, il che è difficilmente accettabile.

Se abbiamo bisogno di un singleton, potremmo considerare la possibilità di delegare la sua istanziazione a un'altra classe - una sorta di factory - che dovrebbe assicurarsi che ci sia una sola istanza del singleton in gioco.

5.2. Questioni implementative

Anche se i singleton sembrano abbastanza semplici, le loro implementazioni possono presentare vari problemi. Tutto si traduce nel fatto che potremmo finire per avere più di una sola istanza della classe.

Sincronizzazione

L'implementazione con un costruttore privato che abbiamo presentato sopra non è thread-safe: funziona bene in un ambiente single-threaded, ma in uno multi-thread, dovremmo usare la tecnica di sincronizzazione per garantire l'atomicità dell'operazione:

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

Notare la parola chiave sincronizzata nella dichiarazione del metodo. Il corpo del metodo ha diverse operazioni (confronto, istanziazione e ritorno).

In assenza di sincronizzazione, v'è la possibilità che due fili interleave loro esecuzioni in modo tale che l'espressione GRADO == nulli restituisce vero per entrambi i fili e, di conseguenza, due istanze di ClassSingleton ottenere creato.

La sincronizzazione potrebbe influire in modo significativo sulle prestazioni. Se questo codice viene richiamato spesso, dovremmo velocizzarlo utilizzando varie tecniche come l' inizializzazione lenta o il blocco a doppio controllo ( tieni presente che questo potrebbe non funzionare come previsto a causa delle ottimizzazioni del compilatore). Possiamo vedere maggiori dettagli nel nostro tutorial "Blocco a doppio controllo con Singleton".

Istanze multiple

Ci sono molti altri problemi con i singleton relativi alla stessa JVM che potrebbero farci finire con più istanze di un singleton. Questi problemi sono piuttosto sottili e daremo una breve descrizione per ciascuno di essi:

  1. Un singleton dovrebbe essere univoco per JVM. Questo potrebbe essere un problema per sistemi distribuiti o sistemi i cui interni sono basati su tecnologie distribuite.
  2. Ogni programma di caricamento classi potrebbe caricare la propria versione del singleton.
  3. Un singleton potrebbe essere sottoposto a garbage collection una volta che nessuno ha un riferimento ad esso. Questo problema non porta alla presenza di più istanze singleton alla volta, ma quando viene ricreata, l'istanza potrebbe differire dalla versione precedente.

6. Conclusione

In questo breve tutorial, ci siamo concentrati su come implementare il pattern Singleton utilizzando solo Java core e su come assicurarci che sia coerente e su come utilizzare queste implementazioni.

L'implementazione completa di questi esempi può essere trovata su GitHub.