Singleton

Pattern descritto in Design Pattern.

Lo scopo del pattern Singleton é quello di assicurare che una classe ha una sola istanza, e gli fornisce un punto di accesso globale.

Esempio

E' un pattern molto usato, in quanto accade molto spesso di dover garantire che esista un solo oggetto di una determinata classe. Un esempio per il nostro mondo fantasy potrebbe essere il genio della lampada, alla Alì Babà. Viene descritto da una classe specifica, dato che ha caratteristiche sue peculiari, e ne esiste uno solo.

E' anche un pattern a cui bisogna dedicare una certa attenzione perché tocca un punto delicato. O meglio, se il nostro singleton si trova ad essere eseguito in un singolo thread le cose sono piuttosto semplici, ma sorgono problemi se lavoriamo, come di solito accade in un ambiente "moderno", in multithreading.

Iniziamo a vedere il caso in cui ci sia un solo thread di esecuzione.

Singleton senza multithreading

Dobbiamo regolamentare la creazione del genio, per questo motivo dichiariamo il costruttore standard privato, impedendo di fatto la creazione di genii all'esterno della classe stessa.

Mettiamo quindi a disposizione un metodo statico, per consuetudine chiamato getInstance(), che crea l'unica instanza della classe, se non é ancora stata creata, e la ritorna al chiamante.

Oltre al meccanismo proprio per la gestione del singleton aggiungiamo alla classe una proprietà per tener traccia del fortunato che viene riconosciuto dal genio come suo capo, e metodi per il get e set del master.

La classe andrebbe estesa per gestire tre desideri e quindi resettare il master a null, ma tralasciamo, per il momento.

Il codice minimale é perciò questo:

package gof.singleton;

public class LampGenie {
private static LampGenie instance = null;

public static LampGenie getInstance() {
if(instance == null)
instance = new LampGenie();
return instance;
}

private Human master = null;

private LampGenie() {}

public Human getMaster() {
return master;
}

boolean setMaster(Human newMaster) {
if(master == null) {
System.out.println("Setting the master to " + newMaster.getName());
master = newMaster;
return true;
}
else {
System.out.println("I already have a master: " + master.getName());
return false;
}
}
}

Singleton in multithreading

Ma che succede alla getInstance() se il codice é eseguito contemporaneamente da più thread? In linea teorica possiamo avere grossi problemi e, come ci insegnano le Leggi di Murphy, possiamo star certi che nel momento meno opportuno qualcosa di sgradevole capiterà anche nella pratica.

Infatti se un thread trova che instance é correntemente pari a null, potrebbe ben essere, che un altro thread abbia appena fatto lo stesso controllo e gli cambi il valore di instance praticamente sotto i piedi, ottenendo così il risultato inaspettato che entrambi i thread finiscono per creare una nuova instance.

Abbiamo infatti due competitori per un unica risorsa il cui accesso, per non evitare questi pasticci, va regolamentato.

Ci sono diversi modi di superare il problema, in funzione del contesto in cui ci troviamo.

In questo caso abbiamo una risorsa, il genio della lampada, che costa poco creare, ed é normalmente usata nel corso della nostra applicazione (che storia sarebbe mai senza genio della lampada?), percui ci viene naturale prendere la decisione di lasciare che ci pensi la Java Virtual Machine a garantire che le cose vadano per il verso giusto. Chiamiamo il costruttore per il singleton al momendo della sua dichiarazione come oggetto statico. Ci penserà la JVM a chiamare il costruttore garantendo l'assenza di conflitti tra thread.

Se ci va bene questo approccio, faremo questi cambiamenti nella classe, che rinominiamo SafeLampGenie:

public class SafeLampGenie {
private static SafeLampGenie instance = new SafeLampGenie();

public static SafeLampGenie getInstance() {
return instance;
}
...

Altrimenti dovremmo riscrivere il metodo getInstance implementando noi la sincronizzazione, ovvero il modo che Java ci mette a disposizione per gestire il problema della concorrenza.

Ne parliamo in un altro post, magari.

Per completare l'esempio manca la definizione della classe Human, che riduciamo ai minimi termini, come segue:

package gof.singleton;

public class Human {
private final String name;

public Human(String name) {
this.name = name;
}

public String getName() {
return name;
}
}


E quindi un funzione main che ci permetta di verificare il funzionamento di quanto abbiamo scritto:

public static void main(String[] args) {
Human tom = new Human("Tom");
Human bob = new Human("Bob");

if(SafeLampGenie.getInstance().getMaster() == null) {
System.out.println("Genie is looking for a master");
}

if(SafeLampGenie.getInstance().setMaster(tom))
System.out.println(tom.getName() + ": I'm the Genie Master!");
if(SafeLampGenie.getInstance().setMaster(bob) == false)
System.out.println(bob.getName() + ": Damn, I'm late!");
}

Nessun commento:

Posta un commento