CodeGym /Java Blog /Random-IT /Classe Singleton Java
John Squirrels
Livello 41
San Francisco

Classe Singleton Java

Pubblicato nel gruppo Random-IT
CIAO! Oggi approfondiremo i dettagli di vari modelli di progettazione, a partire dal modello Java Singleton. Rivediamo: cosa sappiamo dei design pattern in generale? I modelli di progettazione sono le migliori pratiche che possiamo applicare per risolvere una serie di problemi noti. I modelli di progettazione generalmente non sono legati a nessun linguaggio di programmazione. Considerali come una serie di consigli per aiutarti a evitare errori ed evitare di reinventare la ruota.Modelli di progettazione: Singleton - 1

Cos'è un singleton in Java?

Singleton è uno dei modelli di progettazione a livello di classe più semplici. A volte le persone dicono "questa classe è singleton", il che significa che la classe implementa il modello di progettazione singleton. A volte è necessario scrivere una classe in cui limitiamo l'istanza a un singolo oggetto. Ad esempio, una classe responsabile della registrazione o della connessione a un database.Il modello di progettazione singleton descrive come possiamo raggiungere questo obiettivo.Un singleton è un modello di progettazione che fa due cose:
  1. Garantisce che ci sarà sempre e solo un'istanza della classe.

  2. Fornisce un unico punto di accesso globale a tale istanza.

Quindi, ci sono due caratteristiche che sono caratteristiche di quasi tutte le implementazioni del modello singleton:
  1. Un costruttore privato. Ciò limita la possibilità di creare oggetti della classe al di fuori della classe stessa.

  2. Un metodo statico pubblico che restituisce l'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 applicato in vari modi. Ogni opzione è buona e cattiva a modo suo. Come sempre, non esiste un'opzione perfetta qui, ma dovremmo sforzarci di trovarne una. Prima di tutto, decidiamo cosa costituisce buono e cattivo e quali metriche influenzano il modo in cui valutiamo le varie implementazioni del modello di progettazione. Iniziamo con il bene. Ecco i fattori che rendono un'implementazione più succosa e accattivante:
  • Inizializzazione lazy: l'istanza non viene creata finché non è necessaria.

  • Codice semplice e trasparente: questa metrica, ovviamente, è soggettiva, ma è importante.

  • Thread safety: funzionamento corretto in un ambiente multi-thread.

  • Prestazioni elevate in un ambiente multi-thread: blocco dei thread minimo o nullo durante la condivisione di una risorsa.

Ora i contro. Elencheremo i fattori che mettono un'implementazione in cattiva luce:
  • Nessuna inizializzazione pigra: quando la classe viene caricata all'avvio dell'applicazione, indipendentemente dal fatto che sia necessaria o meno (paradossalmente, nel mondo IT è meglio essere pigri)

  • Codice complesso e di difficile lettura. Anche questa metrica è soggettiva. Se i tuoi occhi iniziano a sanguinare, supponiamo che l'implementazione non sia la migliore.

  • Mancanza di sicurezza del filo. In altre parole, "pericolo filo". Operazione errata in un ambiente multi-thread.

  • Scarse prestazioni in un ambiente multi-thread: i thread si bloccano a vicenda sempre o spesso quando condividono una risorsa.

Codice

Ora siamo pronti a considerare varie opzioni di implementazione e indicare i pro e i contro:

Semplice


public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
L'implementazione più semplice. Professionisti:
  • Codice semplice e trasparente

  • Sicurezza del filo

  • Prestazioni elevate in un ambiente multi-thread

Contro:
  • Nessuna inizializzazione pigra.
Nel tentativo di correggere il difetto precedente, otteniamo l'implementazione numero due:

Inizializzazione pigra


public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Professionisti:
  • Inizializzazione pigra.

Contro:
  • Non thread-safe

Questa implementazione è interessante. Possiamo inizializzare pigramente, ma abbiamo perso la sicurezza del thread. Nessun problema: sincronizziamo tutto nell'implementazione numero tre.

Accesso sincronizzato


public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Professionisti:
  • Inizializzazione pigra.

  • Sicurezza del filo

Contro:
  • Scarse prestazioni multithread

Eccellente! Nell'implementazione numero tre, ripristiniamo la sicurezza del thread! Certo, è lento... Ora il metodo getInstance è sincronizzato, quindi può essere eseguito da un solo thread alla volta. Piuttosto che sincronizzare l'intero metodo, in realtà abbiamo solo bisogno di sincronizzare la parte di esso che inizializza la nuova istanza. Ma non possiamo semplicemente usare un blocco sincronizzato per avvolgere la parte responsabile della creazione della nuova istanza. Ciò non garantirebbe la sicurezza del thread. È tutto un po' più complicato. La corretta sincronizzazione può essere vista di seguito:

Chiusura a doppio controllo


public class Singleton {
    private static final 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

Contro:
  • Non supportato nelle versioni precedenti di Java precedenti alla 1.5 (l'utilizzo della parola chiave volatile è stato risolto dalla versione 1.5)

Si noti che affinché questa opzione di implementazione funzioni correttamente, deve essere soddisfatta una delle due condizioni. La variabile INSTANCE deve essere final o volatile . L'ultima implementazione di cui parleremo oggi è il class holder singleton .

Titolare di classe


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.

Contro:
  • Il corretto funzionamento richiede la garanzia che l' oggetto singleton sia inizializzato senza errori. In caso contrario, la prima chiamata al metodo getInstance risulterà in un ExceptionInInitializerError e tutte le chiamate successive produrranno un NoClassDefFoundError .

Questa implementazione è quasi perfetta. È pigro, thread-safe e veloce. Ma ha una sfumatura, come spiegato nell'elenco dei contro. Confronto di varie implementazioni del modello singleton:
Implementazione Inizializzazione pigra Sicurezza del filo Prestazioni multithread Quando usare?
Semplice - + Veloce Mai. O forse quando l'inizializzazione pigra non è importante. Ma non sarebbe mai stato meglio.
Inizializzazione pigra + - Non applicabile Sempre quando il multithreading non è necessario
Accesso sincronizzato + + Lento Mai. O forse quando le prestazioni multithread non contano. Ma non sarebbe mai stato meglio.
Chiusura a doppio controllo + + Veloce In rari casi in cui è necessario gestire le eccezioni durante la creazione del singleton (quando il singleton titolare della classe non è applicabile)
Titolare di classe + + Veloce Ogni volta che è necessario il multithreading ed è garantito che l'oggetto singleton verrà creato senza problemi.

Pro e contro del pattern singleton

In generale, un singleton fa esattamente ciò che ci si aspetta da esso:
  1. Garantisce che ci sarà sempre e solo un'istanza della classe.

  2. Fornisce un unico punto di accesso globale a tale istanza.

Tuttavia, questo modello presenta dei difetti:
  1. Un singleton viola il principio di responsabilità singola: oltre ai suoi doveri diretti, la classe singleton controlla anche il numero di istanze.

  2. La dipendenza di una classe ordinaria da un singleton non è visibile nel contratto pubblico della classe.

  3. Le variabili globali sono cattive. Alla fine, un singleton si trasforma in una pesante variabile globale.

  4. La presenza di un singleton riduce la testabilità dell'applicazione nel suo complesso e delle classi che utilizzano il singleton in particolare.

E questo è tutto! :) Abbiamo esplorato la classe Java Singleton con te. Ora, per il resto della tua vita, quando conversi con i tuoi amici programmatori, puoi menzionare non solo quanto è buono lo schema, ma anche qualche parola su ciò che lo rende cattivo. Buona fortuna per padroneggiare questa nuova conoscenza.

Letture aggiuntive:

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION