2010-03-05 2 views
11

Sto rimuginando su questa lettura & ma riesco a trovare una risposta assoluta autorevole.Il thread di lettura delle variabili Java ArrayList/String/atomic è sicuro?

Ho diverse strutture di dati profonde composte da oggetti contenenti liste di array, stringhe & valori primitivi. Posso garantire che i dati in queste strutture non cambieranno (nessun thread apporterà mai modifiche strutturali alle liste, cambi riferimenti, cambi primitive).

Mi chiedo se la lettura dei dati in queste strutture sia thread-safe; cioè è sicuro leggere le variabili in modo ricorsivo dagli oggetti, iterare le liste di array ecc. per estrarre informazioni dalle strutture in più thread senza sincronizzazione?

+1

'Posso garantire che i dati in queste strutture non cambieranno, quindi in questo caso è perfettamente possibile leggere le variabili contemporaneamente senza sincronizzazione. –

risposta

13

L'unico motivo per cui non sarebbe sicuro è se un thread stava scrivendo su un campo mentre un altro thread leggeva simultaneamente da esso. No race condition esiste se i dati non cambiano. Rendere immutabili gli oggetti è un modo per garantire che siano sicuri. Inizia leggendo l'articolo this da IBM.

+0

Eccellente, grazie. Questo è stato certamente il mio istinto, ma volevo controllare nessuno sa di eventuali cotcha riguardo agli iteratori che usano variabili non filettate ecc. Forse posso smettere di ossessionare e scrivere del codice ora :) –

+0

Sì, praticamente tutto il java.util le raccolte sono thread-safe per l'utilizzo di sola lettura. L'unico problema è LinkedHashMap/Set con un metodo removeEldestEntry personalizzato. – james

+2

Sfortunatamente, questo è sbagliato. In assenza delle condizioni * happen-before * (definite dalle specifiche del linguaggio), parole come "before", "while" e "after" non hanno alcun significato. Rendere immutabili gli oggetti usando 'final' è una buona idea, quindi non mi dilungherò, ma l'idea che la sicurezza del thread possa essere ottenuta senza una barriera di memoria è molto, molto sbagliato. – erickson

2

Proprio come un addendum alle risposte di tutti gli altri: se sei sicuro di dover sincronizzare gli elenchi di array, puoi chiamare Collections.synchronizedList (myList) che ti restituirà un'implementazione thread-safe.

+0

Ci sono casi in cui questo non garantisce la sicurezza del filo. Cosa sono 2 thread ciascuno controllano le dimensioni dell'elenco e quindi rimuovono qualcosa? –

+0

@Chandru quel tipo di atomicità richiede un blocco sincronizzato, ma è garantito solo dal wrapper 'synchronizedList()'; provare a fare la stessa cosa con un 'ArrayList' direttamente sarebbe inaffidabile. – erickson

2

Non riesco a vedere come leggere da ArrayLists, stringhe e valori primitivi che utilizzano più thread dovrebbe essere un problema.

Finché state leggendo, non è necessaria alcuna sincronizzazione. Per le stringhe e i primitivi è sicuramente sicuro poiché sono immutabili. Per ArrayList dovrebbe essere sicuro, ma io non ne ho l'autorizzazione.

+2

Le punture sono internamente imutabili, ma la variabile può cambiare da sotto di te, ciò che dovresti affermare è che le variabili che puntano a String e ai primati dovrebbero essere contrassegnate come 'finali'. –

1

fare NON uso java.util.Vector, utilizzare java.util.Collections.unmodifiableXXX() involucro se veramente sono immodificabile, questo garantisce che non cambierà, e far rispettare tale contratto. Se stanno per essere modificati, quindi utilizzare java.util.Collections.syncronizedXXX(). Ma questo garantisce solo la sicurezza del filo interno. Rendere le variabili final aiuterà anche il compilatore/JIT con le ottimizzazioni.

3

Se i dati non vengono mai modificati dopo la sua creazione, allora si dovrebbe andare bene e le letture saranno thread-safe.

Per sicurezza, è possibile rendere "definitivi" tutti i dati membri e rendere tutte le funzioni di accesso rientranti ove possibile; questo garantisce la sicurezza dei thread e può aiutare a mantenere sicuro il thread del codice se lo cambi in futuro.

In generale, fare in modo che il maggior numero possibile di membri "finali" contribuisca a ridurre l'introduzione di bug, quindi molte persone lo considerano una best practice di Java.

+0

+1 per essere l'unica persona a menzionare "finale" –

+0

la mia risposta parla degli ultimi 7 minuti precedenti :-) –

+0

Come Amir Afghani, l'idea che i dati possano essere condivisi tra thread senza una barriera di memoria è errata e pericolosa.Ma, anche come Amir, hai una buona idea con 'final'. – erickson

4

I membri di un ArrayList non sono protetti da alcuna barriera di memoria, quindi non è garantito che le modifiche siano visibili tra i thread. Questo si applica anche quando l'unico "cambiamento" che viene mai fatto alla lista è la sua costruzione.

Qualsiasi dati condivisi tra thread richiede una "barriera di memoria" per garantirne la visibilità. Ci sono diversi modi per farlo.

In primo luogo, qualsiasi membro dichiarato final e inizializzato in un costruttore è visibile a qualsiasi thread dopo il completamento del costruttore.

Le modifiche a qualsiasi membro dichiarato volatile sono visibili a tutti i thread. In effetti, la scrittura viene "svuotata" da qualsiasi cache alla memoria principale, dove può essere vista da qualsiasi thread che accede alla memoria principale.

Ora diventa un po 'più complicato. Qualsiasi scrittura effettuata da un thread prima dello che le scritture di thread su una variabile volatile vengono svuotate. Allo stesso modo, quando un thread legge una variabile volatile, la sua cache viene cancellata e le letture successive possono ripopolarlo dalla memoria principale.

Infine, un blocco synchronized è come una lettura volatile e in scrittura, con la qualità aggiunta di atomicità. Quando il monitor viene acquisito, la cache di lettura del thread viene cancellata. Quando il monitor viene rilasciato, tutte le scritture vengono scaricate nella memoria principale.

Un modo per fare questo lavoro è di avere il filo che riempie la struttura dei dati condivisi assegnare il risultato a una variabile volatile (o un AtomicReference, o altro oggetto adatto java.util.concurrent). Quando altri thread accedono a tale variabile, non solo sono garantiti per ottenere il valore più recente per quella variabile, ma anche eventuali modifiche apportate alla struttura dati dal thread prima che esso assegni il valore alla variabile.

+0

"I membri di un ArrayList non sono protetti da alcuna barriera di memoria, quindi non c'è alcuna garanzia che le modifiche siano visibili tra i thread" - L'OP afferma esplicitamente che i dati NON stanno cambiando. –

+0

@Amir Afghani - la costruzione dell'oggetto in primo luogo è un "cambiamento" che deve essere visibile ad altri thread. Ecco un altro articolo di Brian Goetz sulla costruzione sicura: http://www.ibm.com/developerworks/java/library/j-jtp0618.html – erickson

+0

Affascinante, grazie erickson. È esattamente il tipo di gotcha di cui avevo paura. La mia lettura del Pattern n. 2 in questa pagina: http://www.ibm.com/developerworks/java/library/j-jtp06197.html conferma il tuo suggerimento per costruire l'albero e poi avere i thread di lettura accedervi attraverso una variabile volatile è davvero una buona idea. Grazie! –