Fa nuovamente ponendo un oggetto in un ConcurrentHashMap causa un “accade-prima” relazione memoria?

StackOverflow https://stackoverflow.com/questions/7809131

Domanda

Sto lavorando con esistente il codice che ha un archivio degli oggetti sotto forma di un ConcurrentHashMap. All'interno della mappa sono conservati oggetti mutabili, l'utilizzo da parte di più thread. Non ci sono due thread cercano di modificare un oggetto alla volta di progettazione. La mia preoccupazione è per quanto riguarda la visibilità delle modifiche tra i fili.

Al momento il codice degli oggetti ha la sincronizzazione sui 'setter' (custoditi dall'oggetto stesso). Non v'è alcuna sincronizzazione sui "getter", né sono i membri volatili. Questo, per me, vorrebbe dire che la visibilità non è garantita. Tuttavia, quando un oggetto viene modificato è ri-put indietro nella mappa (il metodo put() viene chiamato nuovamente, stesso tasto). Questo significa che quando un altro thread tira l'oggetto fuori della mappa, vedrà le modifiche?

Ho ricercato questo qui su StackOverflow, in JCIP , e nella descrizione del pacchetto per java.util. concorrente. Ho fondamentalmente io confuso credo ... ma l'ultima goccia che mi ha fatto questa domanda era dalla descrizione del pacchetto, si legge:

azioni in un filo prima di effettuare un oggetto in qualsiasi raccolta concorrente accadere, prima successive azioni all'accesso o rimozione di tale elemento dalla raccolta in un altro thread.

In relazione alla mia domanda, non "azioni" includono le modifiche agli oggetti memorizzati nella mappa prima la ri-put ()? Se tutto questo non fa risultato di visibilità attraverso i thread, è questo un approccio efficiente? Sono relativamente nuovo per le discussioni e apprezzerei i vostri commenti.

Modifica

Grazie a tutti voi per le risposte! Questa era la mia prima domanda su StackOverflow ed è stato molto utile per me.

Devo andare con ptomli 's risposta, perché credo che sia indirizzata più chiaramente la mia confusione. Vale a dire, che stabilisce un "accade-prima" relazione non incide necessariamente visibilità modifica in questo caso. La mia "domanda del titolo" è costruito poco per quanto riguarda la mia domanda effettiva descritto nel testo. ptomli 's risposta ora Jives con quello che ho letto in JCIP : "Per garantire tutte le discussioni vedono i valori più up-to-date di variabili mutabili condivise, la lettura e la scrittura di discussioni devono sincronizzarsi su un blocco comune" (pagina 37). Re-mettere sul retro oggetto nella mappa non fornisce questo blocco comune per la modifica ai membri dell'oggetto inserito.

Apprezzo tutti i consigli per il cambiamento (oggetti immutabili, ecc), e concordo con tutto il cuore. Ma per questo caso, come già detto non v'è alcuna modifica concomitante causa della facilità di manipolazione del filo. Un thread modifica un oggetto, e un altro filo successivamente legge l'oggetto (con il CHM essendo trasportatore oggetto). Credo CHM è sufficiente a garantire che il filo dopo l'esecuzione vedrà le modifiche dalla prima vista la situazione ho fornito. Tuttavia, credo che molti di voi ha risposto correttamente alla domanda del titolo .

È stato utile?

Soluzione

Credo che la tua domanda riguarda soprattutto gli oggetti che stai memorizzazione nella mappa, e come reagiscono a accesso concorrente, rispetto alla mappa concorrente stesso.

Se le istanze che stai memorizzazione nella mappa sono sincronizzati mutatori, ma non sincronizzato di accesso, quindi non vedo come possano essere thread-safe, come descritto.

Prendere la Map fuori dall'equazione e determinare se le istanze sei memorizzare è thread-safe da soli.

Tuttavia, quando un oggetto viene modificato è re-rimessi in mappa (il metodo put () viene chiamato nuovamente, stesso tasto). Questo significa che quando un altro thread tira l'oggetto fuori della mappa, vedrà le modifiche?

Questo esemplifica la confusione. L'esempio che viene ri-messo in mappa verrà recuperato dalla mappa da un altro thread. Questa è la garanzia della mappa concomitante. Ciò non ha nulla a che fare con la visibilità dello stato dell'istanza immagazzinato in sé.

Altri suggerimenti

Si chiama concurrHashMap.put dopo ogni scrittura a un oggetto. Tuttavia non hai specificato che si chiama anche concurrHashMap.get prima di ogni lettura. Ciò è necessario.

Questo è vero per tutte le forme di sincronizzazione: è necessario disporre di alcuni "punti di controllo" in entrambi i thread. Sincronizzazione solo thread è inutile.

Non ho controllato il codice sorgente di ConcurrentHashMap per assicurarsi che put e grilletto get un accade-prima, ma è logico che dovrebbero.

C'è ancora un problema con il metodo tuttavia, anche se si utilizza sia put e get. Il problema si verifica quando si modifica un oggetto e viene utilizzato (in uno stato incoerente) dall'altro filo prima che sia put. E 'un problema delicato, perché si potrebbe pensare che il vecchio valore sarebbe ancora letto in quanto non è stato put e non causerebbe un problema. Il problema è che quando non si sincronizzano, non sono garantiti per ottenere un oggetto vecchio coerente, ma piuttosto il comportamento non è definito. La JVM può aggiornare qualsiasi parte dell'oggetto negli altri fili, in qualsiasi momento. E 'solo quando si utilizza un po' di sincronizzazione esplicita che si è sicuri che si sta aggiornando i valori in modo coerente in tutti i thread.

Che cosa si potrebbe fare:
(1) sincronizzare tutti gli accessi (getter e setter) per gli oggetti ovunque nel codice. Siate cauti con i setter: fare in modo che non è possibile impostare l'oggetto in uno stato incoerente. Ad esempio, quando si imposta nome e cognome, avendo due setter sincronizzati non è sufficiente: è necessario ottenere il blocco oggetto per entrambe le operazioni insieme
. o
(2) quando si put un oggetto nella mappa, mettere una copia profonda al posto dell'oggetto stesso. In questo modo gli altri thread verrà mai letto un oggetto in uno stato incoerente.

Modifica :
Ho appena notato

codice

Attualmente gli oggetti ha la sincronizzazione sui 'setter' (Custodito dall'oggetto stesso). Non v'è alcuna sincronizzazione sul "Getter", né sono i membri volatili.

Questo non è buono. Come detto in precedenza la sincronizzazione su un solo filo è alcuna sincronizzazione affatto. Si potrebbe eseguire la sincronizzazione su tutti i vostri thread scrittore, ma che importa dal momento che i lettori non avranno i giusti valori.

Credo che questo è stato già detto tutto un paio di risposte, ma per riassumere

Se il codice va

  • CHM # get
  • chiamare vari setter
  • CHM # put

poi la "accade-prima" fornito dal put garantisce che tutte le chiamate mutate vengono eseguite prima put. Ciò significa che qualsiasi get successiva sarà garantita per vedere questi cambiamenti.

Il tuo problema è che lo stato reale dell'oggetto non sarà deterministico perché se il flusso effettivo degli eventi è

  • filo 1: CHM # get
  • filo 1: chiamata setter
  • filo 2: CHM # get
  • filo 1: chiamata setter
  • filo 1: chiamata setter
  • filo 1: CHM # put

allora non v'è alcuna garanzia su ciò che lo stato dell'oggetto sarà in filo 2. Si potrebbe vedere l'oggetto con il valore fornito dal primo setter o non potrebbe.

La copia immutabile sarebbe l'approccio migliore in quanto poi completamente oggetti consistenti sono pubblicati solo. Rendere le varie setter sincronizzati (oi riferimenti sottostanti volatili) ancora non consente di pubblicare stato coerente, significa solo che l'oggetto sarà sempre vedere l'ultimo valore per ogni getter su ogni chiamata.

La mia comprensione è che dovrebbe funzionare per tutti ottiene dopo la ri-put, ma questo sarebbe un metodo molto pericoloso della sincronizzazione.

Quello che succede al ottiene che questo accada prima della ri-put, ma mentre le modifiche stanno accadendo. Possono vedere solo alcune delle modifiche, e l'oggetto avrebbe uno stato incoerente.

Se è possibile, io consiglierei di memorizzare oggetti immutabili nella mappa. Quindi qualsiasi get recupererà una versione dell'oggetto che era corrente quando lo ha fatto il get.

Questo è un frammento di codice da java.util.concurrent.ConcurrentHashMap (Open JDK 7):

  919       public V get(Object key) {
  920           Segment<K,V> s; // manually integrate access methods to reduce overhead
  921           HashEntry<K,V>[] tab;
  922           int h = hash(key.hashCode());
  923           long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
  924           if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
  925               (tab = s.table) != null) {
  926               for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
  927                        (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
  928                    e != null; e = e.next) {
  929                   K k;
  930                   if ((k = e.key) == key || (e.hash == h && key.equals(k)))
  931                       return e.value;
  932               }
  933           }
  934           return null;
  935       }

UNSAFE.getObjectVolatile() IS href="http://www.docjar.com/docs/api/sun/misc/Unsafe.html#getObjectVolatile%28Object,%20long%29" rel="nofollow"> documentato come getter con la semantica volatile interne, così la barriera memoria sarà attraversata quando ottiene il riferimento.

Sì, put incorre in una scrittura volatile, anche se valore-chiave esiste già nella mappa.

utilizzando ConcurrentHashMap di pubblicare gli oggetti attraverso thread è abbastanza efficiente. Gli oggetti non devono essere modificati ulteriormente una volta che sono nella mappa. (Non devono essere rigorosamente immutabile (con campi finali))

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