Singleton Design Pattern

Il singleton è il design pattern che un qualunque programmatore (java) non può non conoscere. Appartiene alla categoria dei design pattern creazionali, ed ha come scopo quello di garantire che di una determinata classe venga creata una ed una sola istanza, e di fornire un punto di accesso globale a tale istanza. A dimostrazione della sua importanza si consideri che tale pattern è utilizzato anche in altri design pattern come Abstract Factory, Builder, Prototype, Facade, etc.

Il concetto di bean di tipo singleton lo troviamo anche in molti framework che implementano la Dependency Injection come Spring o Guice, ma concettualmente sono abbastanza differenti. In Spring, ad esempio, quando un bean ha  scope singleton  significa che può esistere una ed una sola istanza di quel bean all’interno del contesto di Spring (IoC container). Sebbene le due definizioni possano sembrare simili, in realtà non lo sono perché, anche se non espressamente specificato, il design pattern singleton implica che l’istanza della classe sia unica per  class loader .

Ricordiamo che il class loader crea le istanze di classe all’interno della heap memory space di java (si veda Java Heap Space vs Stack Memory per un approfondimento), area di memoria in cui è anche contenuto il container IoC di Spring.

Implementazione Classica

Il modo più semplice per implementare una classe singleton richiede due requisiti:

  1. Rendere privato il costruttore della classe.
  2. Implementare un metodo statico (detto factory) che istanzia e restituisce un oggetto della classe.

Secondo le specifiche date una possibile implementazione della classe singleton sarà quindi la seguente:

Nella classe BasicSingleton, quando viene invocato per la prima volta il metodo getInstance(), un oggetto della classe è creato ed associato alla variabile instance che è poi restituita dal metodo. Poiché instance è una variabile statica, se si invoca nuovamente il metodo getInstance(), anch’esso statico, essendo ora instance non nulla, il metodo restituisce il suo contenuto, invece di istanziare nuovamente la classe BasicSingleton. Questa parte è implementata dalla condizione if.

SI noti che i tale implementazione la creazione dell’oggetto singleton è ritardata fino a quando non se ne abbia effettivamente bisogno (Lazy Initialization) ovvero quando il metodo getInstance() è invocato.

A dimostrazione del corretto funzionamento della classe consideriamo il seguente codice in cui sono utilizzate due variabili foo e bar di tipo BasicSingleton.

Come si si aspetta l’output prodotto sarà “I'm the Foo instance” sebbene il valore è secuperato dall’istanza bar della classe.

Il problema del Multi-threading

L’implementazione vista sopra è corretta fino a quando operiamo in un ambito single-thread, ma in un ambiente multi-thread possono verificarsi comportamenti non corretti nel caso in cui due thread si trovino all’interno della condizione if contemporaneamente. Eventualità che in programmazione multi-thread non possiamo escludere, e che comporterebbe che diversi thread potrebbero ottenere istanze diverse della classe, rompendo di fatto il pattern singleton.

A dimostrazione di ciò implementiamo un thread che semplicemente istanzia la classe BasicSingleton  attraverso il suo metodo factory getInstance():

Modifichiamo poi il costruttore della classeBasicSingleton inserendo la stampa di un messaggio su console, in modo da evidenziare il fatto che uno oggetto della classe è stato creato:

Consideriamo infine il seguente metodo main():

Quello che ci aspetteremmo di avere è la stampa su console di un solo messaggio. Ma eseguendo il programma diverse volte noteremo che in ogni esecuzione il messaggio è stampato più volte, prova del fatto che alcuni thread riescono a istanziare un oggetto diverso della classe, rompendo di fatto il pattern:

Per poter rendere l’implementazione thread safe possiamo adottare diverse soluzioni alternative, che sono mostrate nei paragrafi seguenti.

Eager Singleton

La prima consiste nello spostare la creazione dell’oggetto al di fuori del metodo getInstance(), di fatto facendo in modo che l’istanza sia generata nel momento in cui la classe è caricata dal class loader. Tale tecnica è implementata nella seguente classe EigerSingleton:

Tale approccio è ottimale nel caso in cui la classe singleton non utilizza molte risorse. Sfortunatamente nella maggior parte degli scenari, le classi singleton sono utilizzate per gestire risorse onerose come File System, connessioni a Database, ecc. E’ quindi preferibile ritardare l’istanziazione di tali classi fino a quando il client non ne ha davvero bisogno, ovvero quando è invocato il metodo getInstance().

Inoltre, questo approccio ha anche lo svantaggio di non fornisce alcuna possibilità di poter gestire eventuali eccezioni. Svantaggio che comunque può essere risolto spostando l’istanziazione in un blocco static:

Thread Safe Singleton

La seconda opzione per ottenere un singleton thread-safe è quella di rendere il factory method getInstance() di tipo syncronized, come mostrato nella classe ThreadSafeSingleton riportata di seguito:

Tale implementazione, sebbene funzioni correttamente, è prestazionalmente oneroso a causa del costo computazionale associato all’esecuzione della direttiva synchronized. Per evitare l’inconveniente è possibile utilizzare il Double Checked Locking, ovvero il blocco sincronizzato viene utilizzato all’interno della condizione if con un controllo aggiuntivo per garantire che venga creata una sola istanza della classe singleton. Con tale tecnica il metodo getInstance() diviene:

Bill Pugh Singleton

L’implementazione più utilizzata di classe singleton è quella che fu proposta da William Pugh e che prevede l’utilizzo di una classe helper annidata.

Quando viene caricata la classe BillPughSingleton, la classe annidata SingletonHelper non viene caricata in memoria. Solo quando è invocato il metodo getInstance(), questa classe viene caricata e conseguentemente viene creata anche l’istanza della classe BillPughSingleton. Tale implementazione quindi si comporta come la classe EagerSingleton, ma senza soffrire del problema della inizializzazione anticipata, inoltre è più efficiente della classe ThreadSafeSingleton, in quanto non fa uso dei blocchi synchronized.

Codie Sorgente

Il codice sorgente del progetto è disponibile qui singleton.