Cos'è un singleton?
Un singleton è uno dei modelli di progettazione più semplici che possono essere applicati a una classe. Le persone a volte dicono "questa classe è un singleton", il che significa che questa classe implementa il modello di progettazione singleton. A volte è necessario scrivere una classe per la quale è possibile creare un solo oggetto. Ad esempio, una classe responsabile della registrazione o della connessione a un database. Il modello di progettazione Singleton descrive come possiamo eseguire tale attività. Un singleton è un modello di progettazione che fa due cose:-
Fornisce la garanzia che una classe avrà una sola istanza della classe.
-
Fornisce un punto di accesso globale a un'istanza di questa classe.
-
Costruttore privato. Limita la possibilità di creare oggetti di classe al di fuori della classe stessa.
-
Un metodo statico pubblico che restituisce un'istanza della classe. Questo metodo è chiamato
getInstance
. Questo è il punto di accesso globale all'istanza della classe.
Opzioni di implementazione
Il modello di progettazione singleton viene utilizzato in diversi modi. Ogni opzione è buona e cattiva a modo suo. Qui, come sempre: non esiste un ideale, ma bisogna lottare per raggiungerlo. Ma prima di tutto, definiamo cosa è buono e cosa è cattivo, e quali metriche influenzano la valutazione dell'implementazione di un design pattern. Cominciamo con il positivo. Ecco i criteri che conferiscono succosità e attrattiva all'implementazione:-
Inizializzazione lazy: quando una classe viene caricata mentre l'applicazione è in esecuzione esattamente quando è necessaria.
-
Semplicità e trasparenza del codice: la metrica, ovviamente, è soggettiva, ma importante.
-
Sicurezza del thread: funziona correttamente in un ambiente multi-thread.
-
Prestazioni elevate in un ambiente multi-thread: i thread si bloccano a vicenda minimamente o per niente quando condividono una risorsa.
-
Inizializzazione non pigra: quando una classe viene caricata all'avvio dell'applicazione, indipendentemente dal fatto che sia necessaria o meno (un paradosso, nel mondo IT è meglio essere pigri)
-
Complessità e scarsa leggibilità del codice. Anche la metrica è soggettiva. Daremo per scontato che se il sangue esce dagli occhi, l'implementazione è così così.
-
Mancanza di sicurezza del thread. In altre parole, “pericolo di thread”. Operazione non corretta in un ambiente multi-thread.
-
Scarse prestazioni in un ambiente multi-thread: i thread si bloccano a vicenda continuamente o spesso quando condividono una risorsa.
Codice
Ora siamo pronti a considerare le varie opzioni di implementazione, elencando i pro e i contro:Soluzione semplice
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
L'implementazione più semplice. Professionisti:
-
Semplicità e trasparenza del codice
-
Sicurezza del filo
-
Prestazioni elevate in un ambiente multi-thread
- Inizializzazione non pigra.
Inizializzazione pigra
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Professionisti:
-
Inizializzazione pigra.
-
Non thread-safe
Accesso sincronizzato
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Professionisti:
-
Inizializzazione pigra.
-
Sicurezza del filo
-
Scarse prestazioni in un ambiente multi-thread
getInstance
è sincronizzato e puoi inserirlo solo uno alla volta. Infatti non abbiamo bisogno di sincronizzare l'intero metodo, ma solo quella parte di esso in cui inizializziamo un nuovo oggetto classe. Ma non possiamo semplicemente racchiudere synchronized
la parte responsabile della creazione di un nuovo oggetto in un blocco: questo non garantirà la sicurezza del thread. E' un po' più complicato. Il metodo di sincronizzazione corretto è riportato di seguito:
Chiusura a doppio 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;
}
}
Professionisti:
-
Inizializzazione pigra.
-
Sicurezza del filo
-
Prestazioni elevate in un ambiente multi-thread
-
Non supportato nelle versioni Java precedenti alla 1.5 (la parola chiave volatile è stata corretta nella versione 1.5)
INSTANCE
deve essere final
, o volatile
. L'ultima implementazione di cui parleremo oggi è Class Holder Singleton
.
Titolare della Classe Singleton
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Professionisti:
-
Inizializzazione pigra.
-
Sicurezza del filo.
-
Prestazioni elevate in un ambiente multi-thread.
-
Per un corretto funzionamento è necessario garantire che l'oggetto classe
Singleton
venga inizializzato senza errori. In caso contrario, la prima chiamata al metodogetInstance
terminerà con un erroreExceptionInInitializerError
e tutte le successive fallirannoNoClassDefFoundError
.
Implementazione | Inizializzazione pigra | Sicurezza del filo | Velocità multithread | Quando usare? |
---|---|---|---|---|
Soluzione semplice | - | + | Veloce | Mai. O quando l'inizializzazione pigra non è importante. Ma mai meglio. |
Inizializzazione pigra | + | - | Non applicabile | Sempre quando il multithreading non è necessario |
Accesso sincronizzato | + | + | Lentamente | Mai. O quando la velocità di lavoro con il multithreading non ha importanza. Ma mai meglio |
Chiusura a doppio controllo | + | + | Veloce | In rari casi in cui è necessario gestire le eccezioni durante la creazione di un singleton. (quando il titolare della classe Singleton non è applicabile) |
Titolare della Classe Singleton | + | + | Veloce | Sempre quando è necessario il multithreading e c'è la garanzia che un oggetto di classe singleton verrà creato senza problemi. |
Pro e contro del modello Singleton
In generale, il singleton fa esattamente ciò che ci si aspetta da esso:-
Fornisce la garanzia che una classe avrà una sola istanza della classe.
-
Fornisce un punto di accesso globale a un'istanza di questa classe.
-
Singleton viola il SRP (Principio di responsabilità unica): la classe Singleton, oltre alle sue responsabilità immediate, controlla anche il numero delle sue copie.
-
La dipendenza di una classe o di un metodo regolare da un singleton non è visibile nel contratto pubblico della classe.
-
Le variabili globali sono dannose. Il singleton alla fine si trasforma in una variabile globale pesante.
-
La presenza di un singleton riduce la testabilità dell'applicazione in generale e delle classi che utilizzano il singleton in particolare.
GO TO FULL VERSION