E' da parecchio che noto in questo forum la creazione errata del Singleton, fino ad adesso non sono intervenuto perché non capivo se era un semplice errore di distrazione o una conoscenza errata dell'oggetto. Finalmente ho avuto la risposta, la seconda!
Ho le solite conferme:
- Tutto quello che c'è scritto su wikipedia è da prendere con le pinze;
- I testi in inglese sono sempre superiori a quelli in italiano (e di molto!).
La definizione di un Singleton fatta in questo modo:
public class AlmanacUtility {
private static AlmanacUtility instance=null;
private AlmanacUtility(){
super();
}
public static final AlmanacUtility getInstance(){
if (instance==null){
instance=new AlmanacUtility();
}
return instance;
}
}è estremamente errata! Questo
non è un Singleton e può creare errori estremamente gravi e difficili da individuare.
Mentre la definizione di Singleton data su wikipedia (italia):
public class MioSingolo {
private static MioSingolo istanza = null;
private MioSingolo() {}
public static synchronized MioSingolo getMioSingolo() {
if (istanza == null)
istanza = new MioSingolo();
return istanza;
}
}è (quasi) un Singleton, ma è prestazionalmente scadente.
La definizione di Singleton data su wikipedia (inglese) è invece (la prima delle tre proposte):
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
// Private constructor prevents instantiation from other classes
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}ovvero la più performante e precisa.
Esaminiamo le differenze tra i tre codici.
Il primo
non garantisce che l'oggetto sia un Singleton. Il metodo getInstance non è synchronized, quindi in applicazioni multi-thread può accadere alla prima richiesta del Singleton (e vi assicuro che accade) che due thread arrivano in contemporanea alla riga di codice "if (instance==null)". Per tutti e due i thread l'espressione restituisce "true" e quindi tutti e due i thread passano alla riga successiva "instance=new AlmanacUtility();" creando effettivamente
due istanze diverse della classe. Che quindi non è un Singleton.
Il secondo codice (tralasciando il fatto che non usa il formalismo standard), non è affetto dal problema del primo codice visto che dichiara il metodo "getMioSingolo" (ovvero il metodo convenzionalmente chiamato "getInstance") synchronized. In caso di applicazioni multi-thread, infatti, uno dei due è costretto ad attendere "fuori" dal metodo l'esecuzione dell'altro, solo dopo può entrare ed in quel caso troverebbe la variabile singleton assegnata (diversa da null) restituendo l'unico singleton possibile. Non dichiarando però la variabile "istanza" final, questa può essere di nuovo assegnata a null (all'interno della classe singleton stessa), quindi al prossimo "getMioSingolo" verrebbe instanziato un nuovo Singleton! Vero è che cambia per tutti quello che lo utilizzano, ma anche questo caso è al limite della definizione di Singleton stesso. Inoltre, utilizzando un metodo synchronized, prestazionalmente perde tantissimo.
Il terzo codice non è affetto da nessuno dei precedenti problemi. L'istanza del singleton è delegata al ClassLoader che per definizione è un Singleton gestito dalla macchina virtuale, quindi si ha la sicurezza che viene istanziato uno ed una sola volta. La variabile è final, quindi una volta assegnata non è possibile cambiarla. Non usa metodi synchronized, quindi prestazionalmente è perfetta. In aggiunta ha la chicca dell'override del metodo "clone()" che andando sempre in CloneNotSupportedException fa capire a tutti che la classe non è clonabile (essendo un Singleton, per l'appunto!).
Lo so che sembrano stupide finezze o esagerazioni, ma se chiamiamo una cosa Singleton... Singleton deve essere!
