Un Singleton è un concetto abbastanza semplice, che si può implementare così:

public class MySingleton {
  private static MySingleton instance = null;
  /** Private constructor. */
  private MySingleton() {
    // initialization...
  }
  public static MySingleton getInstance() {
    if (instance == null) {
      instance = new MySingleton();
    }
    return instance;
  }
  // other methods
}

Ma cosa succede quando più thread chiamano getInstance() contemporaneamente?
L’ultimo a completare l’esecuzione del costruttore sovrascrive il valore del campo instance:
questa è l’istanza che verrà restituita alle successive richieste e le altre istanze sono
state inizializzate a tutti gli effetti ma non sono più referenziate e verranno prima o poi
prese dal garbage collector. Oltre ad un temporaneo spreco di memoria, ci possono essere anche
altri effetti collaterali a seconda di cosa contengano il costruttore e gli altri metodi del
singleton.

Una possibile soluzione è quella di istanziare il singleton nella dichiarazione del campo
stesso o usare un costruttore statico: l’istanza verrà creata da Java al momento del primo
accesso a quella classe.

public class MySingleton {
  private static final MySingleton INSTANCE = new MySingleton();
  /** Private constructor. */
  private MySingleton() {
    // initialization...
  }
  public static MySingleton getInstance() {
    return INSTANCE;
  }
  // other methods
}

Altrimenti si può sincronizzare il metodo getInstance():

public class MySingleton {
  private static MySingleton instance = null;
  /** Private constructor. */
  private MySingleton() {
    // initialization...
  }
  public static synchronized MySingleton getInstance() {
    if (instance == null) {
      instance = new MySingleton();
    }
    return instance;
  }
  // other methods
}

Questa soluzione è corretta, ma ha ripercussioni sulla performance, perché tutte le
invocazioni di getInstance() (e possono essere davvero tante!) sono rallentate dai controlli
di sincronizzazione: che spreco per evitare una race condition che può accadere solo sul primo
utilizzo del singleton!

La seguente è la soluzione che preferisco: al costo di un doppio controllo quando l’istanza
non è stata ancora creata, evita che le successive getInstance() incorrano nell’overhead di
sincronizzazione.

public class MySingleton {
  private static volatile MySingleton instance = null;
  /** Private constructor. */
  private MySingleton() {
    // initialization...
  }
  public static MySingleton getInstance() {
    if (instance == null) {
      synchronized (MySingleton.class) {
        if (instance == null) {
          instance = new MySingleton();
        }
      }
    }
    return instance;
  }
  // other methods
}

Il primo if serve a capire se “vale la pena” di entrare nel blocco sincronizzato, mentre le
due righe if-null-then-new devono essere eseguite in maniera atomica per evitare istanziazioni
multiple. In caso di race condition, più thread possono entrare nel blocco synchronized, ma
solo il primo eseguirà la creazione del singleton e gli altri invece troveranno instance non
nullo anche se lo era due righe prima.

Si noti che per funzionare, è richiesto Java 5 ed il campo deve essere impostato come
volatile: altrimenti il sistema non funziona, come dimostra questa disamina tecnica fatta da
persone che ne sanno molto più di me. Devo ringraziare FindBugs, che mi ha permesso di
scoprire il “trucco” del campo volatile e la pagina internet con le spiegazioni: senza quel
modificatore, questa inizializzazione viene marcata con un “insetto giallo” a causa di un
“possible double-check”:

This method may contain an instance of double-checked locking.
This idiom is not correct according to the semantics of the Java memory model.
Annunci