Qual è un modo efficiente per implementare un modello singleton in Java?
Qual è un modo efficiente per implementare un modello singleton in Java?
Risposte:
Usa un enum:
public enum Foo {
INSTANCE;
}
Joshua Bloch ha spiegato questo approccio nel suo discorso efficace su Java Reloaded a Google I / O 2008: collegamento al video . Vedi anche le diapositive 30-32 della sua presentazione ( efficacia_java_reloaded.pdf ):
Il modo giusto di implementare un singleton serializzabile
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Modifica: una parte online di "Efficace Java" dice:
"Questo approccio è funzionalmente equivalente all'approccio di campo pubblico, tranne per il fatto che è più conciso, fornisce gratuitamente i macchinari di serializzazione e fornisce una garanzia coraggiosa contro l'istanza multipla, anche di fronte a sofisticati attacchi di serializzazione o di riflessione. Mentre questo approccio ha ancora ampiamente adottato, un tipo enum a elemento singolo è il modo migliore per implementare un singleton . "
A seconda dell'uso, ci sono diverse risposte "corrette".
Da java5 il modo migliore per farlo è usare un enum:
public enum Foo {
INSTANCE;
}
Pre java5, il caso più semplice è:
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Cannot clone instance of this class");
}
}
Esaminiamo il codice. Innanzitutto, vuoi che la lezione sia definitiva. In questo caso, ho usato la final
parola chiave per far sapere agli utenti che è definitiva. Quindi è necessario rendere privato il costruttore per impedire agli utenti di creare il proprio Foo. Lanciare un'eccezione dal costruttore impedisce agli utenti di usare la riflessione per creare un secondo Foo. Quindi si crea un private static final Foo
campo per contenere l'unica istanza e un public static Foo getInstance()
metodo per restituirlo. La specifica Java garantisce che il costruttore venga chiamato solo quando la classe viene utilizzata per la prima volta.
Quando hai un oggetto molto grande o un codice di costruzione pesante E hai anche altri metodi o campi statici accessibili che potrebbero essere usati prima che sia necessaria un'istanza, allora e solo allora devi usare l'inizializzazione lazy.
È possibile utilizzare a private static class
per caricare l'istanza. Il codice sarebbe quindi simile a:
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
Poiché la linea private static final Foo INSTANCE = new Foo();
viene eseguita solo quando viene effettivamente utilizzata la classe FooLoader, questo si occupa dell'istanza pigra ed è garantito che sia sicuro per i thread.
Quando vuoi anche essere in grado di serializzare il tuo oggetto devi assicurarti che la deserializzazione non crei una copia.
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Il metodo readResolve()
assicurerà che venga restituita l'unica istanza, anche quando l'oggetto è stato serializzato in una precedente esecuzione del programma.
Disclaimer: ho appena sintetizzato tutte le risposte fantastiche e l'ho scritto con le mie parole.
Durante l'implementazione di Singleton abbiamo 2 opzioni
1. Caricamento lento
2. Caricamento anticipato
Il caricamento lento aggiunge un po 'di sovraccarico di bit (molti ad essere sinceri) quindi usalo solo quando hai un oggetto molto grande o un codice di costruzione pesante E hai anche altri metodi o campi statici accessibili che potrebbero essere utilizzati prima che sia necessaria un'istanza, quindi e solo allora è necessario utilizzare l'inizializzazione lenta. In caso contrario, scegliere il caricamento anticipato è una buona scelta.
Il modo più semplice per implementare Singleton è
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
Tutto è buono tranne il suo singleton caricato in anticipo. Proviamo singleton caricato pigro
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
Fin qui tutto bene, ma il nostro eroe non sopravviverà combattendo da solo con più fili malvagi che vogliono molti esempi del nostro eroe. Quindi, consente di proteggerlo dal multi threading male
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
ma non è abbastanza per proteggere l'eroe, Davvero !!! Questo è il meglio che possiamo / dovremmo fare per aiutare il nostro eroe
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
Questo è chiamato "Idioma di blocco a doppio controllo". È facile dimenticare l'affermazione volatile e difficile capire perché sia necessario.
Per dettagli: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Ora siamo sicuri del filo diabolico ma della crudele serializzazione? Dobbiamo assicurarci che anche durante la deserializzazione non venga creato alcun nuovo oggetto
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// Rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
Il metodo readResolve()
assicurerà che venga restituita l'unica istanza, anche quando l'oggetto è stato serializzato in una precedente esecuzione del nostro programma.
Finalmente abbiamo aggiunto una protezione sufficiente contro i thread e la serializzazione, ma il nostro codice sembra voluminoso e brutto. Diamo un cambio al nostro eroe
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Sì, questo è il nostro stesso eroe :)
Dato che la linea private static final Foo INSTANCE = new Foo();
viene eseguita solo quando la classe FooLoader
viene effettivamente utilizzata, questo si occupa dell'istanza pigra,
ed è garantito per essere thread-safe.
E siamo arrivati così lontano, ecco il modo migliore per ottenere tutto ciò che abbiamo fatto è il modo migliore possibile
public enum Foo {
INSTANCE;
}
Quale internamente sarà trattato come
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
Questo è tutto! Niente più paura della serializzazione, dei thread e del brutto codice. Anche ENUMS singleton sono inizializzati pigramente .
Questo approccio è funzionalmente equivalente all'approccio di campo pubblico, tranne per il fatto che è più conciso, fornisce gratuitamente i macchinari di serializzazione e fornisce una garanzia coraggiosa contro l'istanza multipla, anche di fronte a sofisticati attacchi di serializzazione o di riflessione. Mentre questo approccio deve ancora essere ampiamente adottato, un tipo enum a elemento singolo è il modo migliore per implementare un singleton.
-Joshua Bloch in "Efficace Java"
Ora potresti aver capito perché ENUMS è considerato il modo migliore per implementare Singleton e grazie per la tua pazienza :)
Aggiornato sul mio blog .
serialVersionUID
di 0L
. Terzo problema: nessuna personalizzazione: qualsiasi metodo writeObject, readObject, readObjectNoData, writeReplace e readResolve specifico per la classe definito dai tipi di enum viene ignorato durante la serializzazione e la deserializzazione.
La soluzione pubblicata da Stu Thompson è valida in Java5.0 e successive. Preferirei non usarlo perché penso che sia soggetto a errori.
È facile dimenticare l'affermazione volatile e difficile capire perché sia necessario. Senza la volatile questo codice non sarebbe più sicuro per i thread a causa dell'antipasto a doppio controllo. Per ulteriori informazioni, consultare il paragrafo 16.2.4 della concorrenza Java in pratica . In breve: questo modello (precedente a Java5.0 o senza l'istruzione volatile) potrebbe restituire un riferimento all'oggetto Bar che è (ancora) in uno stato errato.
Questo modello è stato inventato per l'ottimizzazione delle prestazioni. Ma questa non è più una vera preoccupazione. Il seguente codice di inizializzazione lenta è veloce e, soprattutto, più facile da leggere.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()
. (E se getBar
viene chiamato "troppo presto", allora affronterai lo stesso problema, indipendentemente dal modo in cui i single sono implementati.) Puoi vedere la classe pigra che carica il codice qui sopra: pastebin.com/iq2eayiR
Thread sicuro in Java 5+:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
EDIT : presta attenzione al volatile
modificatore qui. :) È importante perché senza di esso, altri thread non sono garantiti dal JMM (Java Memory Model) per vedere le modifiche al suo valore. La sincronizzazione non se ne occupa: serializza solo l'accesso a quel blocco di codice.
EDIT 2 : La risposta di @Bno descrive in dettaglio l'approccio raccomandato da Bill Pugh (FindBugs) ed è discutibile meglio. Vai a leggere e vota anche la sua risposta.
Dimentica l' inizializzazione lenta , è troppo problematica. Questa è la soluzione più semplice:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
Assicurati di averne davvero bisogno. Fai un google per "singleton anti-pattern" per vedere alcuni argomenti contrari. Non c'è nulla di intrinsecamente sbagliato in esso, suppongo, ma è solo un meccanismo per esporre alcune risorse / dati globali, quindi assicurati che questo sia il modo migliore. In particolare, ho trovato l'iniezione di dipendenza più utile in particolare se si utilizzano anche test unitari perché DI consente di utilizzare risorse derise a scopo di test.
Sono sconcertato da alcune delle risposte che suggeriscono DI come alternativa all'uso dei singleton; questi sono concetti non correlati. È possibile utilizzare DI per iniettare istanze singleton o non singleton (ad es. Per thread). Almeno questo è vero se usi Spring 2.x, non posso parlare per altri framework DI.
Quindi la mia risposta all'OP sarebbe (in tutti tranne il codice di esempio più banale) a:
Questo approccio offre una piacevole architettura disaccoppiata (e quindi flessibile e testabile) in cui l'uso di un singleton è un dettaglio dell'implementazione facilmente reversibile (a condizione che tutti i singleton che utilizzi siano thread-safe, ovviamente).
TicketNumberer
elemento che deve avere una singola istanza globale e dove vuoi scrivere una classe TicketIssuer
che contiene una riga di codice int ticketNumber = ticketNumberer.nextTicketNumber();
. Nel pensiero singleton tradizionale, la precedente riga di codice dovrebbe essere qualcosa di simile TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. Nel pensiero di DI, la classe avrebbe un costruttore simile public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.
main
metodo dell'app (o uno dei suoi servitori) creerebbe la dipendenza e quindi chiamerebbe il costruttore. Fondamentalmente, l'uso di una variabile globale (o di un metodo globale) è solo una forma semplice del modello di localizzazione del servizio temuto e può essere sostituito con l'iniezione di dipendenza, proprio come qualsiasi altro uso di quel modello.
Considera davvero perché hai bisogno di un singleton prima di scriverlo. C'è un dibattito quasi religioso sull'uso di questi che puoi facilmente imbatterti in google singletons in Java.
Personalmente cerco di evitare i singleton il più spesso possibile per molte ragioni, ancora una volta la maggior parte delle quali può essere trovata su google google. Sento che abbastanza spesso i singoli sono abusati perché sono facili da capire da tutti, sono usati come meccanismo per ottenere dati "globali" in un progetto OO e sono usati perché è facile aggirare la gestione del ciclo di vita degli oggetti (o pensando davvero a come puoi fare A dall'interno B). Guarda cose come Inversion of Control (IoC) o Dependency Injection (DI) per un bel mezzo.
Se ne hai davvero bisogno, Wikipedia ha un buon esempio di una corretta implementazione di un singleton.
Di seguito sono riportati 3 diversi approcci
1) Enum
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Doppio controllo Blocco / Caricamento lento
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public static DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
3) Metodo statico di fabbrica
/**
* Singleton pattern example with static factory method
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
Uso lo Spring Framework per gestire i miei singoli. Non impone la "singolarità" della classe (cosa che non si può fare comunque se ci sono caricatori di classi multiple) ma fornisce un modo davvero semplice per costruire e configurare fabbriche diverse per la creazione di diversi tipi di oggetti.
Versione 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if(instance == null) {
instance = new MySingleton();
}
return instance;
}
}
Caricamento lento, thread-safe con blocco, basse prestazioni a causa di synchronized
.
Versione 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public final static MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
Caricamento lento, thread threading senza blocco, ad alte prestazioni.
Se non hai bisogno di un caricamento lento, prova semplicemente
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Se vuoi un caricamento lento e vuoi che il tuo Singleton sia sicuro per i thread, prova il modello di doppio controllo
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Poiché non è garantito il funzionamento del modello di doppio controllo (a causa di un problema con i compilatori, non ne so più nulla al riguardo), potresti anche provare a sincronizzare l'intero metodo getInstance o creare un registro per tutti i tuoi Singleton.
volatile
Direi Enum singleton
Singleton usando enum in Java è generalmente un modo per dichiarare enum singleton. Enum singleton può contenere una variabile di istanza e un metodo di istanza. Per semplicità, si noti inoltre che se si utilizza un metodo di istanza diverso da quello necessario, è necessario garantire la sicurezza del thread di tale metodo se influisce affatto sullo stato dell'oggetto.
L'uso di un enum è molto facile da implementare e non ha inconvenienti per quanto riguarda gli oggetti serializzabili, che devono essere elusi in altri modi.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Puoi accedervi Singleton.INSTANCE
, molto più facilmente che chiamare il getInstance()
metodo su Singleton.
1.12 Serializzazione delle costanti di Enum
Le costanti di Enum sono serializzate in modo diverso rispetto ai normali oggetti serializzabili o esternalizzabili. La forma serializzata di una costante enum consiste unicamente nel suo nome; i valori dei campi della costante non sono presenti nel modulo. Per serializzare una costante enum,
ObjectOutputStream
scrive il valore restituito dal metodo name della costante enum. Per deserializzare una costante enum,ObjectInputStream
legge il nome costante dallo stream; la costante deserializzata è quindi ottenuta chiamando iljava.lang.Enum.valueOf
metodo, passando come argomento il tipo enum della costante e il nome della costante ricevuta. Come altri oggetti serializzabili o esternalizzabili, le costanti enum possono funzionare come target dei riferimenti posteriori che compaiono successivamente nel flusso di serializzazione.Il processo per cui le costanti enum vengono serializzati non può essere personalizzato: qualsiasi classe-specifica
writeObject
,readObject
,readObjectNoData
,writeReplace
, ereadResolve
metodi definiti dai tipi enum vengono ignorati durante la serializzazione e deserializzazione. Allo stesso modo, anche qualsiasi dichiarazioneserialPersistentFields
oserialVersionUID
campo viene ignorata - tutti i tipi di enum hanno un valore fissoserialVersionUID
di0L
. Non è necessario documentare campi e dati serializzabili per i tipi di enum, poiché non vi è alcuna variazione nel tipo di dati inviati.
Un altro problema con i Singleton convenzionali è che una volta implementata l' Serializable
interfaccia, non rimangono più Singleton perché il readObject()
metodo restituisce sempre una nuova istanza come il costruttore in Java. Questo può essere evitato usando readResolve()
e scartando l'istanza appena creata sostituendola con singleton come di seguito
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Questo può diventare ancora più complesso se la tua classe Singleton mantiene lo stato, poiché è necessario renderli transitori, ma con Enum Singleton, la serializzazione è garantita da JVM.
Buona lettura
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
public class Test {
private static volatile Test test;
private Test(){}
public static Test getTest() {
if(test == null) {
synchronized(Test.class) {
if(test == null){test = new Test();
}
}
}
return test;
}
3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
public class Test {
private Test(){}
private static class TestHolder{
private static final Test test = new Test();
}
public static Test getInstance(){
return TestHolder.test;
}
}
4- enum singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
Potrebbe essere un po 'tardi al gioco su questo, ma ci sono molte sfumature nell'implementazione di un singleton. Il modello di supporto non può essere utilizzato in molte situazioni. E IMO quando si utilizza una volatile, è necessario utilizzare anche una variabile locale. Cominciamo dall'inizio e ripetiamo il problema. Vedrai cosa intendo.
Il primo tentativo potrebbe assomigliare a questo:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
Qui abbiamo la classe MySingleton che ha un membro statico privato chiamato INSTANCE e un metodo statico pubblico chiamato getInstance (). La prima volta che viene chiamato getInstance (), il membro INSTANCE è null. Il flusso ricadrà quindi nella condizione di creazione e creerà una nuova istanza della classe MySingleton. Le chiamate successive a getInstance () troveranno che la variabile INSTANCE è già impostata e quindi non creano un'altra istanza di MySingleton. Ciò garantisce che vi sia una sola istanza di MySingleton condivisa tra tutti i chiamanti di getInstance ().
Ma questa implementazione ha un problema. Le applicazioni multi-thread avranno una condizione di competizione sulla creazione della singola istanza. Se più thread di esecuzione colpiscono il metodo getInstance () contemporaneamente (o intorno), ciascuno vedrà il membro INSTANCE come null. Ciò comporterà che ogni thread crei una nuova istanza MySingleton e successivamente imposti il membro INSTANCE.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Qui abbiamo usato la parola chiave sincronizzata nella firma del metodo per sincronizzare il metodo getInstance (). Questo sicuramente risolverà le nostre condizioni di gara. I thread ora bloccano e inseriscono il metodo uno alla volta. Ma crea anche un problema di prestazioni. Questa implementazione non solo sincronizza la creazione della singola istanza, ma sincronizza tutte le chiamate a getInstance (), comprese le letture. Le letture non devono essere sincronizzate in quanto restituiscono semplicemente il valore di ISTANZA. Poiché le letture costituiranno la maggior parte delle nostre chiamate (ricordate, l'istanza si verifica solo alla prima chiamata), incorreremo in un superamento delle prestazioni non necessario sincronizzando l'intero metodo.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
Qui abbiamo spostato la sincronizzazione dalla firma del metodo a un blocco sincronizzato che avvolge la creazione dell'istanza di MySingleton. Ma questo risolve il nostro problema? Bene, non stiamo più bloccando le letture, ma abbiamo anche fatto un passo indietro. Più thread colpiranno il metodo getInstance () contemporaneamente o all'incirca nello stesso tempo e vedranno tutti il membro INSTANCE come null. Colpiranno quindi il blocco sincronizzato dove si otterrà il blocco e creerà l'istanza. Quando quel thread esce dal blocco, gli altri thread contenderanno il blocco e uno per uno ogni thread cadrà nel blocco e creerà una nuova istanza della nostra classe. Quindi siamo tornati da dove siamo partiti.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Qui emettiamo un altro controllo da INSIDE il blocco. Se il membro INSTANCE è già stato impostato, ignoreremo l'inizializzazione. Questo si chiama blocco con doppio controllo.
Questo risolve il nostro problema di istanza multipla. Ma ancora una volta, la nostra soluzione ha presentato un'altra sfida. Altri thread potrebbero non "vedere" che il membro INSTANCE è stato aggiornato. Ciò è dovuto al modo in cui Java ottimizza le operazioni di memoria. I thread copiano i valori originali delle variabili dalla memoria principale nella cache della CPU. Le modifiche ai valori vengono quindi scritte e lette da quella cache. Questa è una funzionalità di Java progettata per ottimizzare le prestazioni. Ma questo crea un problema per la nostra implementazione singleton. Un secondo thread - in fase di elaborazione da una CPU o core diverso, utilizzando una cache diversa - non vedrà le modifiche apportate dal primo. Questo farà sì che il secondo thread vedrà il membro INSTANCE come null forzando la creazione di una nuova istanza del nostro singleton.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Risolviamo questo usando la parola chiave volatile sulla dichiarazione del membro INSTANCE. Questo dirà al compilatore di leggere sempre e scrivere nella memoria principale e non nella cache della CPU.
Ma questo semplice cambiamento ha un costo. Poiché stiamo bypassando la cache della CPU, subiremo un calo delle prestazioni ogni volta che operiamo sul membro instabile INSTANCE, cosa che facciamo 4 volte. Ricontrolliamo l'esistenza (1 e 2), impostiamo il valore (3) e quindi restituiamo il valore (4). Si potrebbe sostenere che questo percorso è il caso marginale poiché creiamo l'istanza solo durante la prima chiamata del metodo. Forse un colpo di performance alla creazione è tollerabile. Ma anche il nostro caso d'uso principale, si legge, opererà sull'elemento volatile due volte. Una volta per verificare l'esistenza e di nuovo per restituire il suo valore.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
Poiché l'hit di performance è dovuto all'operare direttamente sull'elemento volatile, impostiamo una variabile locale sul valore della volatile e operiamo invece sulla variabile locale. Ciò ridurrà il numero di volte in cui operiamo sulla volatile, recuperando così alcune delle nostre prestazioni perse. Nota che dobbiamo reimpostare la nostra variabile locale quando inseriamo il blocco sincronizzato. Ciò garantisce che sia aggiornato con qualsiasi modifica avvenuta mentre stavamo aspettando il blocco.
Ho scritto un articolo su questo recentemente. Decostruire The Singleton . Puoi trovare maggiori informazioni su questi esempi e un esempio del modello "holder" lì. C'è anche un esempio reale che mostra l'approccio volatile ricontrollato. Spero che sia di aiuto.
class MySingleton
- forse dovrebbe essere final
?
BearerToken
istanza non è statica perché fa parte di BearerTokenFactory
- che è configurata con un server di autorizzazione specifico. Potrebbero esserci molti BearerTokenFactory
oggetti - ognuno con la propria "cache" BearerToken
che distribuisce fino alla sua scadenza. Il hasExpired()
metodo su BeraerToken
viene chiamato nel get()
metodo factory per assicurarsi che non distribuisca un token scaduto. Se scaduto, verrà richiesto un nuovo token per il server di autorizzazione. Il paragrafo che segue il blocco di codice spiega questo in modo più dettagliato.
Ecco come implementare un semplice singleton
:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
Ecco come creare correttamente il tuo pigro singleton
:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
getInstance()
. Ma in effetti se non hai altri metodi statici nella tua classe Singleton
e chiami solo getInstance()
non c'è vera differenza.
È necessario un doppio controllo del linguaggio se è necessario caricare pigramente la variabile di istanza di una classe. Se è necessario caricare pigramente una variabile statica o un singleton, è necessario inizializzare il linguaggio dell'intestatario della domanda .
Inoltre, se il singleton deve essere seriliazble, tutti gli altri campi devono essere transitori e il metodo readResolve () deve essere implementato per mantenere invariante l'oggetto singleton. Altrimenti, ogni volta che l'oggetto viene deserializzato, verrà creata una nuova istanza dell'oggetto. Ciò che readResolve () fa è sostituire il nuovo oggetto letto da readObject (), che ha costretto quel nuovo oggetto a essere garbage collection in quanto non vi è alcuna variabile che si riferisce ad esso.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
Vari modi per creare un oggetto singleton:
Secondo Joshua Bloch - Enum sarebbe il migliore.
puoi usare anche il doppio controllo del blocco.
È possibile utilizzare anche la classe statica interna.
Enum singleton
Il modo più semplice per implementare un Singleton sicuro per i thread è usare un Enum
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
Questo codice funziona dall'introduzione di Enum in Java 1.5
Doppio bloccaggio controllato
Se vuoi codificare un singleton "classico" che funziona in un ambiente multithread (a partire da Java 1.5), dovresti usare questo.
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
}
Questo non è thread-safe prima di 1.5 perché l'implementazione della parola chiave volatile era diversa.
Caricamento anticipato Singleton (funziona anche prima di Java 1.5)
Questa implementazione crea un'istanza per il singleton quando la classe viene caricata e fornisce la sicurezza del thread.
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
Per JSE 5.0 e versioni successive adottare l'approccio Enum, altrimenti utilizzare l'approccio statico del singleton holder ((un approccio di caricamento lento descritto da Bill Pugh). La soluzione Latter è anche sicura per i thread senza richiedere costrutti di linguaggio speciali (ovvero volatile o sincronizzato).
Un altro argomento spesso usato contro i Singleton sono i loro problemi di testabilità. I singleton non sono facilmente derisibili a scopo di test. Se questo risulta essere un problema, mi piace apportare la seguente leggera modifica:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
Il setInstance
metodo aggiunto consente di impostare un'implementazione mockup della classe singleton durante il test:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Questo funziona anche con approcci di inizializzazione precoce:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Ciò ha lo svantaggio di esporre questa funzionalità anche alla normale applicazione. Altri sviluppatori che lavorano su quel codice potrebbero essere tentati di usare il metodo "setInstance" per alterare una funzione specifica e quindi cambiare l'intero comportamento dell'applicazione, quindi questo metodo dovrebbe contenere almeno un buon avviso nel suo javadoc.
Tuttavia, per la possibilità di test di simulazione (quando necessario), questa esposizione al codice può essere un prezzo accettabile da pagare.
classe singleton più semplice
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
Penso ancora che dopo java 1.5, enum sia la migliore implementazione singleton disponibile in quanto garantisce anche che negli ambienti multi-thread - venga creata solo un'istanza.
public enum Singleton{
INSTANCE;
}
e il gioco è fatto !!!
Dai un'occhiata a questo post.
Esempi di modelli di progettazione GoF nelle librerie principali di Java
Dalla sezione "Singleton" della migliore risposta,
Singleton (riconoscibile con metodi di creazione che restituiscono sempre la stessa istanza (di solito di se stesso))
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
Puoi anche imparare l'esempio di Singleton dalle stesse classi native di Java.
Il miglior modello singleton che abbia mai visto utilizza l'interfaccia del fornitore.
Vedi sotto:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
A volte un semplice "static Foo foo = new Foo();
" non è abbastanza. Pensa solo ad alcuni inserimenti di dati di base che vuoi fare.
D'altra parte dovresti sincronizzare qualsiasi metodo che istanzia la variabile singleton in quanto tale. La sincronizzazione non è male come tale, ma può portare a problemi di prestazioni o blocco (in casi molto rari usando questo esempio. La soluzione è
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
Ora che succede? La classe viene caricata tramite il caricatore di classi. Immediatamente dopo che la classe è stata interpretata da un array di byte, la VM esegue il blocco statico {} . questo è l'intero segreto: il blocco statico viene chiamato solo una volta, il momento in cui la classe (nome) del pacchetto dato viene caricata da questo caricatore di una classe.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
if (INSTANCE != null)
throw new IllegalStateException (“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
Dato che abbiamo aggiunto la parola chiave Synchronized prima di getInstance, abbiamo evitato le condizioni di competizione nel caso in cui due thread chiamassero getInstance contemporaneamente.