Design Pattern Singleton in Java

Singleton Pattern Java - Articolo che fornisce una visione completa del Design Pattern Singleton in Java.

il
Software Developer, Collaboratore di IProgrammatori

Il Design Pattern Singleton applicato a Java.


“First, solve the problem. Then, write the code”
John Johnson

Il Singleton è un design pattern di tipo creazionale appartenente alla Programmazione Orientata ad Oggetti.
In quanto pattern di tipo creazionale ha il compito di risolvere problematiche inerenti la creazione e le istanze degli oggetti, ma la sua funzione è precisamente quella di gestire n possibili istanze di una determinata classe in maniera efficiente e condivisa. Il precedente si traduce nell’utilizzo di una classe in maniera simile al modificatore d’accesso static.

Singleton

Questo articolo ha come obiettivo la descrizione del pattern Singleton applicato al linguaggio Java, non soffermandosi alla sua definizione base ma mostrando un caso d’uso che, come dice il pattern stesso, garantisce in ogni caso un’unica istanza della nostra classe.
Prima di procedere con le spiegazioni vediamo un reale caso d’uso.

I casi d’uso del Singleton

Quali sono le motivazioni per cui dovremmo rendere un oggetto di tipo Singleton?

Caso 1
Definire una classe che gestisca una stampante virtuale condivisa da un intero ufficio.

Caso 2
Definire una classe che gestisca i log applicativi interfacciandosi ad un file di testo.

Caso 3
Definire una classe che simuli il meccanismo di Inversion of Control, al pari di un framework quale Spring o Hibernate.
Appare evidente da subito una necessità comune a tutte le richieste: la gestione condivisa della medesima risorsa. Sarebbe infatti non solo inefficiente ma scorretto gestire questo genere di richieste effettuando sempre una nuova istanza della classe rappresentante la nostra risorsa (la stampante ad esempio).


Singleton Single-thread Java

Analizziamo adesso la versione del Singleton basilare.


Si implementi una classe di cui sia possibile un’unica istanza -> le possibili n richieste di una nuova istanza devono sempre restituire un riferimento allo stesso oggetto.


Il seguente codice richiama un’ipotetica classe di tipo Singleton mostrandone i pregi.

Singleton mySingleton = Singleton.getInstance();
mySingleton.intProperty = 2;
Singleton myNewSingleton = Singleton.getInstance();
System.out.println(myNewSingleton.intProperty); //la stampa deve produrre: 2

Già vedendo le poche righe mostrate si intuisce la necessità del modificatore static.
Diamo qui una banale definizione del modificatore static suggerendo però una più attenta lettura della discussione al seguente link:
https://www.iprogrammatori.it/forum-programmazione/java/static-instanza-t28478.html

Il modificatore (non d’accesso) static puo essere applicato alla definizione:
• di una variabile d’istanza (alias un attributo o proprietà appartenente ad una classe)
• di un metodo.

Esso si occupa di rendere proprio della classe, non più di un oggetto, un determinato metodo o variabile. Questo si concretizza nella condivisione dei valori che porta in sè una variabile per tutte le istanze effettuate di quella classe: la modifica del valore della variabile su un’istanza si ripercuote su tutte le istanze.

Procediamo quindi per step alla definizione di una classe Singleton che dichiara al suo interno una variabile condivisa da tutte le istanze (quindi statica):

public class Singleton {
      private static Singleton instance;
}


La classe non deve consentire in alcun modo la chiamata al costruttore, sostituendo la possibilità di istanziare un oggetto con l’operatore new tramite un metodo getInstance (anch’esso statico):

public class Singleton {
      private static Singleton instance;
      private Singleton() {}
      public static Singleton getInstance() {
            return instance;
      }
}


Allo stato attuale però il metodo getInstance restituirà sempre null visto che la variabile d’istanza non è stata popolata in alcun modo. Dobbiamo fare in modo che solo e soltanto la prima chiamata al metodo restituisca una nuova istanza (tramite l’operatore new); tutte le altre chiamate restituiranno semplicemente la variabile già popolata (l’istanza condivisa):

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

Singleton Multi-thread Java

Siamo a buon punto. Il codice precedente è un esempio di Singleton che calza in molte situazioni, ma dobbiamo renderlo thread safe. Questo significa che il precedente codice, in un contesto multithread potrebbe fare cilecca!
Ora, per rendere un pezzo di codice thread safe, alias sincrono, ci basta utilizzare la keyword synchronized.
Ma dove? Porre il metodo intero come sincrono non è efficiente, non è pensabile porre un (thread) lock ad un intero metodo che possa in qualche modo infastidire un altro chiamante. Ecco perchè il controllo sul thread lo possiamo immettere internamente al primo controllo:

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


Splendido, ma c’è ancora una miglioria da apportare. Il precedente codice sotto determinate architetture hardware comporta delle possibilità che l’oggetto recuperato non sia quello desiderato (per maggiori informazioni è possibile consultare gli studi del “Double-Checked Locking” di David Bacon); una soluzione, possibile soltanto da Java 5 in poi, è utilizzando la keyword volatile.
Probabilmente parliamo di una delle keyword di java meno note a chi approccia il codice da pochi anni, ecco perchè è necessaria una breve descrizione.
La keyword volatile consente ad una variabile di essere letta e scritta non nella memoria temporanea della CPU o in cache ma nella memoria principale della macchina d’esecuzione, evitando così problemi di inconsistenza dei dati. Senza scendere troppo nei dettagli di gestione della memoria basti pensare che una variabile volatile viene posta in una zona di memoria a parte e pertanto non c’è dubbio che l’accesso a questa variabile ci darà sempre il risultato desiderato. In Java possiamo concludere il nostro esempio con la versione definitiva e multi thread di un Singleton:

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