2010-08-30 8 views
6

Solo Java 5 e versioni successive. Assumi un computer con memoria condivisa multiprocessore (probabilmente ne stai usando uno proprio ora).Java keyword sincronizzata svuota la cache?

Ecco un codice per la pigra inizializzazione 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 evitare che l'ottimizzatore di riscrittura getInstance() come segue (che sarebbe corretto in un sequenziale programma):

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 riscrivere il codice, se instance non è dichiarato volatile è ancora garantito per essere lavata alla memoria quando il blocco synchronized viene chiuso e letto dalla memoria quando viene inserito il blocco synchronized?

EDIT: Ho dimenticato di rendere getInstance() statico. Non penso che cambi la validità delle risposte; sapevi tutti cosa intendevo.

+0

Suggerimento: è possibile utilizzare 'javap -c com.example.ClassName' per disassemblare il codice in modo da poter verificare come è stato compilato e se il compilatore ha modificato/ottimizzato l'uno o l'altro. Ho controllato per te con quello di JDK 1.6 e il compilatore non ha rimosso il nidificato 'if'. – BalusC

+1

L'uso di javap è interessante, ma l'ottimizzatore di javac è in genere piuttosto scarso. Di proposito. Il vero ottimizzatore è nel compilatore JIT in fase di esecuzione. – Darron

+0

Mi scuso per aver controllato solo una delle risposte come "La risposta". Tutti loro meritano finora di essere controllati, ma mi permettono solo di controllarne uno. –

risposta

15

Sì, deve essere dichiarato volatile. Anche allora, si consiglia di non utilizzare il blocco a doppio controllo. Questo (o per essere precisi, il modello di memoria Java) aveva un difetto grave che permetteva la pubblicazione di oggetti parzialmente implementati. Questo problema è stato risolto in Java5, tuttavia DCL è un idioma obsoleto e non è più necessario utilizzarlo, utilizzare invece lo lazy initialization holder idiom.

Da Java Concurrency in Practice, sezione 16.2:

Il vero problema con DCL è il presupposto che la cosa peggiore che può accadere quando si legge un riferimento all'oggetto condiviso senza sincronizzazione è vedere erroneamente un valore stantio (in questo caso , null); in tal caso l'idioma DCL compensa questo rischio riprovando con il blocco trattenuto. Ma il caso peggiore è in realtà considerevolmente peggiore: è possibile vedere un valore corrente del riferimento, ma valori obsoleti per lo stato dell'oggetto, il che significa che l'oggetto potrebbe essere visto in uno stato non valido o errato.

Successive modifiche nel JMM (Java 5.0 e versioni successive) hanno permesso di lavorare DCL seresource è fatto volatile, e l'impatto sulle prestazioni di questa è piccola dal volatile letture sono di solito solo leggermente più costoso di volatile legge. Tuttavia, questo è un idioma la cui utilità è ampiamente passata - le forze che lo hanno motivato (sincronizzazione lenta e non controllata, avvio lento della JVM) non sono più in gioco, rendendolo meno efficace come ottimizzazione. L'idioma di inizializzazione pigro offre gli stessi vantaggi ed è più facile da capire.

+1

+1 Molto interessante. Sono sempre battuto da Stackoverflower. Questo è un esercizio di umiltà senza fine. – gawi

+1

Se Edsger Dijkstra fosse ancora con noi, probabilmente osserverebbe che se i programmatori esperti sono sempre sorpresi dallo stackoverflow, la professione è in cattivo stato. –

+0

l'idioma della classe titolare funziona solo per singleton. non un meccanismo generale di inizializzazione pigra. – irreputable

1

Sì, esempio deve essere volatili utilizzando ricontrollato bloccaggio in Java, perché altrimenti l'inizializzazione di MySingleton potrebbe esporre un oggetto parzialmente costruito per il resto del sistema. È anche vero che i thread si sincronizzeranno quando raggiungeranno l'istruzione "sincronizzata", ma in questo caso è troppo tardi.

Wikipedia e un paio di altre domande di Stack Overflow hanno una buona discussione di "blocco a doppio controllo", quindi consiglierei di leggerlo. Vorrei anche consigliare di non usarlo a meno che la profilazione non mostri un reale bisogno di prestazioni in questo particolare codice.

+0

(dall'articolo javaworld) Un prefisso comunemente suggerito è quello di dichiarare volatile il campo di risorse di SomeClass. Tuttavia, mentre il JMM impedisce alle scritture di variabili volatili di essere riordinate l'una rispetto all'altra e assicura che vengano scaricate immediatamente nella memoria principale, è comunque possibile riordinare le letture e le scritture delle variabili volatili rispetto alle letture e alle scritture non volatili. Ciò significa - a meno che tutti i campi Risorsa non siano volatili - il thread B può ancora percepire l'effetto del costruttore come accade dopo che la risorsa è stata impostata per fare riferimento alla risorsa appena creata. – gawi

+1

Questo problema è stato risolto in Java5, sebbene valga la pena sottolineare che nelle macchine virtuali meno recenti anche il volatile non risolverà il problema. – samkass

1

C'è un linguaggio più sicuro e più leggibile per pigri inizializzazione single 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 più prolisso modello ricontrollato blocco, si deve dichiarare il campo volatile, come altri hanno già notato.

0

No. Non deve essere volatile. Vedi How to solve the "Double-Checked Locking is Broken" Declaration in Java?

precedenti tentativi tutti falliti, perché se si può barare java ed evitare volatili/sincronizzazione, Java possono imbrogliare e vi darà una visione non corretta dell'oggetto. tuttavia la nuova semantica final risolve il problema. se si ottiene un riferimento a un oggetto (per lettura ordinaria), è possibile leggere in modo sicuro i suoi campi final.

+1

Ancora, l'idioma di inizializzazione pigro ottiene lo stesso, in un modo più chiaro, con meno codice (quindi meno possibilità di errori) e senza alcuna necessità di sincronizzazione esplicita. –

+0

che funziona solo per singleton. non per creare pigramente numero dinamico di oggetti in fase di runtime. – irreputable

+1

Questa domanda riguarda espressamente i singleton. – erickson