Domanda

Java 5 e solo sopra. Supponiamo che un computer a memoria condivisa multiprocessore (probabilmente ne stai usando uno in questo momento).

Ecco un codice per l'inizializzazione pigra di un singleton:

public final class MySingleton {
  private static MySingleton instance = null;
  private MySingleton() { } 
  public static MySingleton getInstance() {
    if (instance == null) {
      synchronized (MySingleton.class) {
        if (instance == null) {
          instance = new MySingleton();
        }
      }
    }
    return instance;
  }
}

Fa instance devono essere dichiarati volatile Al fine di impedire all'ottimizzatore di riscrivere getInstance () come segue (che sarebbe corretto in un programma sequenziale):

public static MySingleton getInstance() {
  if (instance == null) {
    synchronized (MySingleton.class) {
      // instance must be null or we wouldn't be here  (WRONG!)
      instance = new MySingleton();
    }
  }
}

Supponendo che l'ottimizzatore non riscrive il codice, se instance non è dichiarato volatile È ancora garantito per essere arrossati in memoria quando il synchronized il blocco viene uscito e leggi dalla memoria quando il synchronized Il blocco è inserito?

EDIT: ho dimenticato di rendere statico getInstance (). Non credo che cambi la validità delle risposte; Sapete tutti cosa intendevo.

È stato utile?

Soluzione

Sì, instance dovrebbe essere dichiarato volatile. Anche allora, si consiglia di non utilizzare il blocco a doppio controllo. (O per essere precisi, il modello di memoria Java) aveva un grave difetto che consentiva la pubblicazione di oggetti parzialmente implementati. Questo è stato fissato in java5, ancora dcl è un linguaggio obsoleto e non è più necessario usarlo: usa il Idioma del titolare dell'inizializzazione pigra invece.

Da Concorrenza di Java nella pratica, Sezione 16.2:

Il vero problema con DCL è il presupposto che la cosa peggiore che possa accadere quando si legge un riferimento a oggetti condivise senza sincronizzazione è vedere erroneamente un valore stantio (in questo caso, null); In tal caso il idioma DCL compensa questo rischio provando di nuovo con il blocco detenuto. Ma il caso peggiore è in realtà considerevolmente peggiore: è possibile vedere un valore attuale del riferimento ma valori stantii per lo stato dell'oggetto, il che significa che l'oggetto potrebbe essere visto essere in uno stato non valido o errato.

Le successive modifiche al JMM (Java 5.0 e successive) hanno permesso a DCL di funzionare Se resource è realizzato volatile, e l'impatto sulle prestazioni di questo è piccolo da allora volatile Le letture sono generalmente solo leggermente più costose delle letture non volatili. Tuttavia, questo è un idioma la cui utilità è in gran parte superata: le forze che l'hanno motivata (sincronizzazione non contesa lenta, startup JVM lenta) non sono più in gioco, rendendola meno efficace come ottimizzazione. Il pigro idioma del titolare dell'inizializzazione offre gli stessi vantaggi ed è più facile da capire.

Altri suggerimenti

Sì, l'istanza deve essere volatile utilizzando il blocco a doppio controllo in Java, poiché altrimenti l'inizializzazione di Mysingleton potrebbe esporre un oggetto parzialmente costruito al resto del sistema. È anche vero che i thread si sincronizzeranno quando raggiungono la dichiarazione "sincronizzata", ma è troppo tardi in questo caso.

Wikipedia E un paio di altre domande di overflow dello stack hanno buone discussioni su "bloccaggio a doppio controllo", quindi consiglierei di leggerlo. Inoltre consiglierei di non usarlo a meno che la profilazione non mostri un vero bisogno di prestazioni in questo particolare codice.

C'è un linguaggio più sicuro e più leggibile per l'inizializzazione pigra dei singoli Java:

class Singleton {

  private static class Holder {
    static final Singleton instance = create();
  }

  static Singleton getInstance() { 
    return Holder.instance; 
  }

  private Singleton create() {
    ⋮
  }

  private Singleton() { }

}

Se si utilizza il modello di bloccaggio più verboso a doppio controllo, devi dichiarare il campo volatile, come altri hanno già notato.

No. Non deve essere volatile. Vedere Come risolvere la dichiarazione "Blocking a doppio controllo è rotto" in Java?

I tentativi precedenti sono falliti perché se puoi imbrogliare Java ed evitare volatile/sincronizzazione, Java può imbrogliare e darti una visione errata dell'oggetto. Tuttavia il nuovo final La semantica risolve il problema. Se ottieni un riferimento a oggetti (tramite lettura ordinaria), puoi leggere in sicurezza il suo final campi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top