[Design patterns in Java] Singleton
Il pattern Singleton garantisce che per una classe ci sia una sola istanza e fornisce un unico punto di accesso alla stessa.
CLASSIFICAZIONE
Il Singleton è classificato tra i pattern creazionali.
PROBLEMA E CAMPO DI APPLICAZIONE
Vogliamo che una classe denominata Singleton
(ma che possiamo rinominare nel modo più opportuno) abbia al più un'istanza e che si possa accedere a quest'ultima in maniera univoca.
Tale necessità può presentarsi in diverse occasioni: accesso ai database, configurazioni, logging ecc.
SOLUZIONE
Per prima cosa occorre evitare che si possano creare istanze della classe, per cui rendiamo privato il costruttore con
private Singleton();
L'unica istanza disponibile dovrà essere rappresentata da una variabile privata statica di tipo Singleton.
La variabile è privata perchè deve essere accessibile attraverso un opportuno metodo e statica in quanto tale metodo è statico (legato alla classe e non ad una singola istanza).
private static Singleton instance;
Dobbiamo creare un metodo pubblico e statico getInstance()
che restituirà la variabile statica suddetta, effettuando l'inizializzazione alla prima richiesta.
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
Questa soluzione iniziale presenta un grosso difetto, ovvero non è thread-safe.
In parole povere, in un ambiente multi-threading, più thread possono accedere alla classe e nella fase di inizializzazione (quando l'istanza non è ancora disponibile) possono creare più oggetti, violando il vincolo che è alla base del pattern.
Ad esempio supponiamo che ci siano in esecuzione due thread t1
e t2
e che l'istanza della classe Singleton non sia stata ancora creata.
Il thread t1
richiama il metodo getInstance()
ma l'esecuzione viene sospesa subito dopo aver verificato che la variabile instance
è impostata a null
. Intanto il thread t2
richiama lo stesso metodo, completando l'esecuzione del codice e istanziando un oggetto.
A questo punto t1 viene riattivato e a sua volta crea un'ulteriore istanza.
In definitiva ci sono due oggetti della classe Singleton. E questo non va bene.
Quali sono le possibili soluzioni?
Osserviamo che la creazione dell'istanza è di tipo lazy ovvero posticipata al momento in cui viene richiesta per la prima volta tramite la getInstance()
.
Se il metodo non viene mai invocato non ci sarà alcun oggetto della classe Singleton.
Possiamo creare l'istanza al caricamento della classe dal parte del classloader con la seguente modifica
private static Singleton instance = new Singleton();
Una soluzione semplice da realizzare ma con lo svantaggio di creare l'istanza anche se questa non dovesse mai essere richiesta/utilizzata.
In alternativa possiamo rendere synchronized
il metodo getInstance()
a costo di impattare sulle performance dell'applicazione in quanto la sincronizzazione, che introduce un overhead dovuto all'acquisizione del lock, sarebbe applicata ad ogni invocazione del metodo stesso.
Meglio spostare la sincronizzazione nel blocco if in corrispondenza della creazione dell'istanza, così che venga applicata una sola volta, effettuando allo stesso tempo un doppio controllo. Questo rende il codice un po' più complesso e richiede che la variabile instance
sia dichiarata come volatile
per essere condivisa correttamente tra i vari thread.
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
[VIDEO]