Java Singleton e sincronizzazione


117

Si prega di chiarire le mie domande riguardanti Singleton e Multithreading:

  • Qual è il modo migliore per implementare Singleton in Java, in un ambiente multithread?
  • Cosa succede quando più thread tentano di accedere getInstance() al metodo contemporaneamente?
  • Possiamo fare singleton getInstance() synchronized?
  • La sincronizzazione è davvero necessaria quando si usano le classi Singleton?

Risposte:


211

Sì, è necessario. Esistono diversi metodi che è possibile utilizzare per ottenere la sicurezza dei thread con l'inizializzazione lenta:

Sincronizzazione draconiana:

private static YourObject instance;

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

Questa soluzione richiede che ogni thread sia sincronizzato quando in realtà solo i primi devono esserlo.

Doppio controllo della sincronizzazione :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Questa soluzione garantisce che solo i primi pochi thread che tentano di acquisire il singleton debbano passare attraverso il processo di acquisizione del blocco.

Inizializzazione su richiesta :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Questa soluzione sfrutta le garanzie del modello di memoria Java sull'inizializzazione della classe per garantire la sicurezza dei thread. Ogni classe può essere caricata una sola volta e verrà caricata solo quando necessario. Ciò significa che la prima volta che getInstanceviene chiamata, InstanceHolderverrà caricata e instanceverrà creata, e poiché questo è controllato da ClassLoaders, non è necessaria alcuna sincronizzazione aggiuntiva.


23
Avviso: fare attenzione con la sincronizzazione ricontrollata. Non funziona correttamente con le JVM pre-Java 5 a causa di "problemi" con il modello di memoria.
Stephen C

3
-1 Draconian synchronizatione Double check synchronizationgetInstance () - il metodo deve essere statico!
Triste

2
@PeterRader Non è necessario che lo siano static, ma potrebbe avere più senso se lo fossero. Modificato come richiesto.
Jeffrey

4
L'implementazione del blocco con doppio controllo non è garantito per funzionare. In realtà è spiegato nell'articolo che hai citato per il doppio controllo del blocco. :) C'è un esempio che usa volatile che funziona correttamente per 1.5 e versioni successive (il blocco a doppia verifica è semplicemente rotto sotto 1.5). L'inizializzazione su richiesta del titolare citata anche nell'articolo sarebbe probabilmente una soluzione più semplice nella tua risposta.
bloccatoj

2
@MediumOne AFAIK, rnon è necessario per la correttezza. È solo un'ottimizzazione per evitare di accedere al campo volatile, poiché è molto più costoso dell'accesso a una variabile locale.
Jeffrey

69

Questo modello esegue un'inizializzazione pigra thread-safe dell'istanza senza sincronizzazione esplicita!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Funziona perché utilizza il caricatore di classi per eseguire gratuitamente tutta la sincronizzazione: MySingleton.Loadersi accede prima alla classe all'interno del getInstance()metodo, quindi la Loaderclasse viene caricata quando getInstance()viene chiamata per la prima volta. Inoltre, il programma di caricamento classi garantisce che tutta l'inizializzazione statica sia completa prima di ottenere l'accesso alla classe: questo è ciò che ti offre la sicurezza dei thread.

È come una magia.

In realtà è molto simile al pattern enum di Jhurtado, ma trovo il pattern enum un abuso del concetto enum (sebbene funzioni)


11
La sincronizzazione è ancora presente, è solo applicata dalla JVM invece che dal programmatore.
Jeffrey

@ Jeffrey Hai ragione, naturalmente - stavo scrivendo tutto (vedi modifiche)
Bohemian

2
Capisco che non fa differenza per la JVM, sto solo dicendo che ha fatto la differenza per me per quanto riguarda il codice auto-documentato. Non ho mai visto tutto maiuscolo in Java senza la parola chiave "final" prima (o enum), ho un po 'di dissonanza cognitiva. Per qualcuno che programma Java a tempo pieno, probabilmente non farebbe differenza, ma se salti avanti e indietro tra i linguaggi, è utile essere espliciti. Idem per i neofiti. Tuttavia, sono sicuro che ci si possa adattare a questo stile abbastanza velocemente; tutto maiuscolo è probabilmente sufficiente. Non intendo fare il pignolo, mi è piaciuto il tuo post.
Ruby

1
Ottima risposta, anche se non ne ho ricevuto una parte. Puoi approfondire "Inoltre, il programma di caricamento classi garantisce che tutta l'inizializzazione statica sia completa prima che tu possa accedere alla classe: questo è ciò che ti garantisce la sicurezza dei thread." , come questo aiuta nella sicurezza dei thread, sono un po 'confuso al riguardo.
gaurav jain

2
@ wz366 in realtà, sebbene non sia necessario, sono d'accordo per motivi di stile (dato che è effettivamente definitivo perché nessun altro codice può accedervi) finaldovrebbe essere aggiunto. Fatto.
Bohemian

21

Se stai lavorando su un ambiente multithread in Java e hai bisogno di garantire che tutti quei thread accedano a una singola istanza di una classe, puoi usare un Enum. Ciò avrà l'ulteriore vantaggio di aiutarti a gestire la serializzazione.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

e poi fai in modo che i tuoi thread usino la tua istanza come:

Singleton.SINGLE.myMethod();

8

Sì, è necessario getInstance()sincronizzarsi. In caso contrario, potrebbe verificarsi una situazione in cui è possibile creare più istanze della classe.

Considera il caso in cui hai due thread che chiamano getInstance()allo stesso tempo. Ora immagina che T1 venga eseguito subito dopo il instance == nullcontrollo e quindi T2 venga eseguito. A questo punto l'istanza non viene creata o impostata, quindi T2 passerà il controllo e creerà l'istanza. Ora immagina che l'esecuzione torni a T1. Ora viene creato il singleton, ma T1 ha già fatto il controllo! Si procederà a realizzare nuovamente l'oggetto! Effettuare la getInstance()sincronizzazione previene questo problema.

Esistono alcuni modi per rendere i singleton thread-safe, ma getInstance()sincronizzarli è probabilmente il più semplice.


Aiuterà inserendo il codice di creazione dell'oggetto nel blocco sincronizzato, invece di eseguire la sincronizzazione dell'intero metodo?
RickDavis

@RaoG No. Vuoi sia il controllo che la creazione nel blocco di sincronizzazione. È necessario che queste due operazioni avvengano insieme senza interruzioni o la situazione che ho descritto sopra potrebbe accadere.
Oleksi

7

Enum singleton

Il modo più semplice per implementare un singleton thread-safe è 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

Chiusura a doppio controllo

Se vuoi codificare un singleton “classico” che funzioni 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 della 1.5 perché l'implementazione della parola chiave volatile era diversa.

Caricamento anticipato di Singleton (funziona anche prima di Java 1.5)

Questa implementazione crea un'istanza del singleton quando la classe viene caricata e fornisce la thread safety.

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");
  }

}

2

È inoltre possibile utilizzare un blocco di codice statico per creare un'istanza al caricamento della classe e prevenire i problemi di sincronizzazione del thread.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

@Vimsha Un paio di altre cose. 1. Dovresti fare il instancefinale 2. Dovresti rendere getInstance()statico.
John Vint

Cosa faresti se desideri creare un thread in singleton.
Arun George

@ arun-george usa un pool di thread, un pool di thread singolo se necessario e circondalo con un while (true) -try-catch-throwable se vuoi assicurarti che il tuo thread non muoia mai, non importa quale errore?
tgkprog

0

Qual è il modo migliore per implementare Singleton in Java, in un ambiente multithread?

Fare riferimento a questo post per il modo migliore per implementare Singleton.

Qual è un modo efficiente per implementare un pattern singleton in Java?

Cosa succede quando più thread tentano di accedere al metodo getInstance () contemporaneamente?

Dipende dal modo in cui hai implementato il metodo. Se usi il doppio bloccaggio senza variabile volatile, potresti ottenere un oggetto Singleton parzialmente costruito.

Fare riferimento a questa domanda per maggiori dettagli:

Perché è volatile utilizzato in questo esempio di blocco con doppio controllo

Possiamo sincronizzare getInstance () di singleton?

La sincronizzazione è davvero necessaria quando si usano le classi Singleton?

Non richiesto se si implementa Singleton nei modi seguenti

  1. intitalizzazione statica
  2. enum
  3. LazyInitalaization con Initialization-on-demand_holder_idiom

Fare riferimento a questa domanda per maggiori dettagli

Java Singleton Design Pattern: domande


0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Fonte: Java efficace -> Articolo 2

Suggerisce di usarlo, se sei sicuro che la classe rimarrà sempre singleton.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.