2012-01-11 13 views
8

Domanda relativa al JMM e alla semantica riguardante i campi volatili scritti in un blocco sincronizzato, ma letti non sincronizzati.Necessità di scrittura di array volatile mentre si è nel blocco sincronizzato

In una versione iniziale del codice seguente, non stavo sincronizzando l'accesso poiché non era necessario per i requisiti precedenti (e abusando di un'assegnazione automatica this.cache = this.cache assicurava una semantica volatile di scrittura). Alcuni requisiti sono stati modificati, rendendo necessaria la sincronizzazione per garantire che non vengano inviati aggiornamenti duplicati. La domanda che ho è che il blocco di sincronizzazione preclude di richiedere l'auto assegnazione del campo volatile?

// Cache of byte[] data by row and column. 
    private volatile byte[][][] cache; 

    public byte[] getData(int row, int col) 
    { 
    return cache[row][col]; 
    } 

    public void updateData(int row, int col, byte[] data) 
    { 
    synchronized(cache) 
    { 
     if (!Arrays.equals(data,cache[row][col])) 
     { 
     cache[row][col] = data; 

     // Volatile write. 
     // The below line is intentional to ensure a volatile write is 
     // made to the array, since access via getData is unsynchronized. 
     this.cache = this.cache; 

     // Notification code removed 
     // (mentioning it since it is the reason for synchronizing). 
     } 
    } 
    } 

Senza sincronizzazione, ritengo che sia tecnicamente necessaria la scrittura volatili assegnazione sé (anche se le bandiere IDE come avere alcun effetto). Con il blocco sincronizzato, penso che sia ancora necessario (dal momento che la lettura non è sincronizzata), ma voglio solo confermarlo poiché appare ridicolo nel codice se non è effettivamente richiesto. Non sono sicuro se ci sono garanzie che non sono a conoscenza tra la fine di un blocco sincronizzato e una lettura volatile.

risposta

4

Sì, è ancora necessario scrivere in modo volatile, secondo il modello di memoria Java. Non c'è un ordine di sincronizzazione di sbloccare cache ad una successiva lettura volatile del cache: sblocco -> volatileRead non garantisce la visibilità. È necessario sbloccare -> blocco o volatileWrite -> volatileLettura.

Tuttavia, le JVM reali hanno garanzie di memoria molto più forti. Solitamente unlock e volatileWrite hanno lo stesso effetto memoria (anche se sono su variabili diverse); uguale a blocco e volatileLettura.

Quindi abbiamo un dilemma qui.La raccomandazione tipica è che si dovrebbe seguire rigorosamente le specifiche. A meno che tu non abbia una conoscenza molto ampia della questione. Ad esempio, un codice JDK può impiegare alcuni trucchi che non sono teoricamente corretti; ma il codice ha come target una JVM specifica e l'autore è un esperto.

Il sovraccarico relativo della scrittura extra volatile non sembra essere comunque così grande.

Il codice è corretto ed efficiente; tuttavia è al di fuori di schemi tipici; Vorrei modificarlo un po 'come:

private final byte[][][] cacheF = new ...; // dimensions fixed? 
    private volatile byte[][][] cacheV = cacheF; 

    public byte[] getData(int row, int col) 
    { 
    return cacheV[row][col]; 
    } 

    public void updateData(int row, int col, byte[] data) 
    { 
    synchronized(cacheF) 
    { 
     if (!Arrays.equals(data,cacheF[row][col])) 
     { 
     cacheF[row][col] = data; 

     cacheV = cacheF; 
     } 
    } 
    } 
+0

Grazie per la risposta, è chiaro e conferma ciò che sospettavo. Anche il consiglio di avere due variabili di cache è probabilmente una buona idea poiché evita che qualcuno rimuova l'auto assegnazione (nonostante i commenti avvertano di non farlo). –

3

L'autoassegnazione si assicura che un altro thread leggerà il riferimento di matrice impostato e non un altro riferimento di matrice. Ma potresti avere un thread che modifica l'array mentre un altro thread lo legge.

Sia le letture che le scritture sull'array devono essere sincronizzate. Inoltre, non memorizzerei e restituirò ciecamente gli array a/da una cache. Un array è una struttura di dati mutabile, non a prova di thread e qualsiasi thread potrebbe corrompere la cache mutando la matrice. Dovresti prendere in considerazione la creazione di copie difensive e/o restituire un elenco non modificabile piuttosto che un array di byte.

+0

Grazie per i suggerimenti, e sono d'accordo che in un mondo ideale, le copie difensivi sarebbe una buona cosa da fare, ma le matrici nella cache qui non si sta per essere modificato in modo che il guadagno in termini di prestazioni rispetto al non fare copie sia preferibile (e se questo cambia la cache potrebbe essere completata da un'implementazione che restituisce copie difensive). Sono ragionevolmente sicuro che il codice come scritto sia corretto, non sono sicuro di poter rimuovere l'auto-assegnazione volatile o no ora che ho aggiunto la sincronizzazione parziale (per il mio commento su un'altra risposta, credo di averne ancora bisogno, solo voglio verificarlo). –

+0

+1 per la risposta, ma ho accettato l'altra risposta poiché risponde più direttamente alla domanda. –

3

Un indice di scrittura su un array volatile non ha effettivamente effetti di memoria. Cioè, se hai già istanziato l'array, avere il campo dichiarato come volatile non ti darà la semantica della memoria che stai cercando quando assegni ad elementi all'interno dell'array.

In altre parole

private volatile byte[][]cache = ...; 
cache[row][col] = data; 

ha la stessa semantica di memoria come

private final byte[][]cache = ...; 
cache[row][col] = data; 

A causa di questo è necessario sincronizzare su tutti lettura e scrittura alla matrice. E quando dico "semantica memoria stessa" intendo dire che non vi è alcuna garanzia che i thread leggeranno il valore più aggiornato di cache[row][col]

+0

Ho avuto la stessa sensazione, ma dal momento che in seguito scrive sull'array volatile, e poiché il volatile ha un "effetto a cascata", su tutto ciò che è stato scritto prima della scrittura nel campo volatile, penso che non ci sia alcun problema di visibilità, infatti . È molto fragile e sarebbe molto più chiaro e robusto con un getData sincronizzato. –

+1

Il problema però è che non esiste alcuna relazione prima tra l'archivio non volatile e l'archivio volatile di this.cache = this.cache –

+1

Ce n'è uno, perché la scrittura non volatile nella cache [riga] [col] viene creato prima della scrittura volatile nella cache, nella stessa discussione. E la scrittura volatile accade prima della lettura della cache. –

Problemi correlati