2013-02-19 18 views
31

questo è un passaggio da JavaDoc relativo a ConcurrentHashMap. Dice che le operazioni di recupero generalmente non si bloccano, quindi potrebbero sovrapporsi alle operazioni di aggiornamento. Significa che il metodo get() non è thread-safe?ConcurrentHashMap è totalmente sicuro?

"Tuttavia, anche se tutte le operazioni sono thread-safe, recupero operazioni non comportano bloccaggio, e non c'è alcun supporto per bloccare l'intera tabella in modo da impedire qualsiasi accesso. Questa classe è pienamente interoperabile con Hashtable in programmi basati sulla sua sicurezza filo ma non delle sue modalità di sincronizzazione.

operazioni di recupero (compresi get) generalmente non ostruire, così può sovrapposizione con le operazioni di aggiornamento (anche mettere e togliere). Retrievals riflettono i risultati del più recente compl aggiorna le operazioni di aggiornamento tenendo il loro esordio. "

+0

Se sto leggendo correttamente, vuol dire che il recupero restituirà sempre i risultati dell'ultimo aggiornamento per completare -all'ora in cui è stato avviato il recupero-. Ciò significa che un aggiornamento completo può verificarsi tra il momento in cui un recupero inizia e termina e non cambierà il risultato di tale recupero. – Aurand

risposta

8

ConcurrentHashmap.get() è thread-safe, nel senso che

  • Non sarà gettare alcuna eccezione, tra cui ConcurrentModificationException
  • verrà restituito un risultato che era vero ad un certo (recente) volta in passato, . Ciò significa che due chiamate back-to-back per ottenere possono restituire risultati diversi. Naturalmente, questo è vero anche per qualsiasi altro Map.
+0

Che cosa intendi per "due chiamate consecutive"? – user697911

+0

Chiama "get()" e salva il risultato. Chiama "get()" una seconda volta e confronta i risultati. Questo è ciò che significa "back-to-back". –

+2

Le chiamate back to back per ottenere possono restituire risultati diversi se è stata eseguita un'intervallo 'put()' o un'altra operazione che modifica la mappa. – Gray

4

Significa solo che quando un thread è in fase di aggiornamento e un thread sta leggendo non è garantito che quello che ha chiamato il metodo ConcurrentHashMap per primo, nel tempo, avrà prima l'operazione.

Pensa a un aggiornamento sull'articolo che indica dove si trova Bob. Se un thread chiede dove si trova Bob all'incirca nello stesso momento in cui un altro thread si aggiorna per dire che è arrivato "dentro", non è possibile prevedere se il thread del lettore otterrà lo stato di Bob come "interno" o "esterno". Anche se il thread di aggiornamento chiama per primo il metodo, il thread del lettore potrebbe ottenere lo stato "esterno".

I thread non causeranno l'un l'altro problemi. Il codice è ThreadSafe.

Un thread non entra in un ciclo infinito o inizia a generare NullPointerExceptions o ottiene "itside" con metà del vecchio stato e metà del nuovo.

+0

Un modello di blocco [doppio controllo] (https://en.wikipedia.org/wiki/Double-checked_locking) risolverebbe il problema? Forzando il thread che sta cercando il valore "inside" da verificare due volte, il thread dello scrittore avrà la possibilità di aggiornare il valore? In questo modo si bloccherebbero le scritture ma non si leggerà? –

33

Il metodo get() è thread-safe e gli altri utenti hanno fornito risposte utili su questo particolare problema.

Tuttavia, anche se ConcurrentHashMap è un thread-safe drop-in sostituzione per HashMap, è importante rendersi conto che se si sta facendo più operazioni potrebbe essere necessario modificare il codice in modo significativo. Ad esempio, prendere questo codice:

if (!map.containsKey(key)) 
    return map.put(key, value); 
else 
    return map.get(key); 

In un ambiente multi-thread, questa è una condizione di competizione. Devi usare lo ConcurrentHashMap.putIfAbsent(K key, V value) e prestare attenzione al valore di ritorno, che ti dice se l'operazione put è andata a buon fine o meno. Leggi i documenti per maggiori dettagli.


Risposta a un commento che richiede chiarimenti sul motivo per cui questa è una condizione di competizione.

immaginare che ci sono due fili A, B che stanno per mettere due diversi valori nella mappa, v1 e v2 rispettivamente, avente la stessa chiave. La chiave inizialmente non è presente nella mappa. Essi interleave in questo modo:

  • filettatura A chiamate containsKey e scopre che la chiave non è presente, ma è immediatamente sospeso.
  • Thread B chiamate containsKey e scopre che la chiave non è presente e ha il tempo di inserire il suo valore v2.
  • Thread A riprende e inserisce v1, sovrascrittura "pacifica" (poiché put è thread-safe) il valore inserito dalla discussione B.

Ora infilare B "pensa" si è inserito con successo un proprio valore v2, ma la mappa contiene v1. Questo è davvero un disastro perché il thread B può chiamare v2.updateSomething() e "penserà" che i consumatori della mappa (ad es. Altri thread) abbiano accesso a quell'oggetto e vedranno quell'aggiornamento forse importante ("like: questo indirizzo IP del visitatore sta provando a eseguire un DOS, rifiutare tutte le richieste d'ora in poi "). Invece, l'oggetto sarà presto raccolto e perso.

+0

"non una sostituzione drop-in thread-safe per HashMap". Uh, lo è certamente. Hai ragione che ci sono condizioni di gara con più operazioni, ma ciò non impedisce a ConcurrentHashMap di essere thread-safe. – Gray

+0

Per la sostituzione drop-in intendo: si modifica la tua HashMap su una ConcurrentHashMap e tutte le operazioni comuni sulla mappa (come il "fatto a pezzi se assente" - ho visto miliardi di volte) sono automagicamente thread-safe. – gd1

+0

concordato. Ma la tua affermazione "ConcurrentHashMap non è una sostituzione drop-in sicura per thread di HashMap" è estremamente fuorviante. Direi che non è corretto. – Gray

1

HashMap è suddiviso in "buckets" basato su hashCode. ConcurrentHashMap utilizza questo fatto. Il suo meccanismo di sincronizzazione è basato su bucket di blocco piuttosto che sull'intero Map. In questo modo, pochi thread possono scrivere contemporaneamente su diversi bucket (un thread può scrivere su un bucket alla volta).

La lettura da ConcurrentHashMapquasi non utilizza i meccanismi di sincronizzazione. Ho detto quasi perché utilizza la sincronizzazione quando otterrà il valore null. In base al fatto che ConcurrentHashMap non può memorizzare valori null (sì, anche i valori non possono essere nulli) se otterrà quel valore deve significare che la lettura è stata invocata durante la scrittura della coppia chiave-valore (dopo l'immissione è stata creata per chiave, ma il suo valore non era ancora impostato: era null). In tal caso, il thread di lettura dovrà attendere fino al termine della scrittura.

Quindi i risultati da read() si baseranno sullo stato corrente della mappa. Se si legge il valore della chiave che si trovava nel mezzo dell'aggiornamento, è probabile che si ottenga un valore vecchio poiché il processo di scrittura non è ancora terminato.

9

È thread-safe. Tuttavia, il modo in cui è thread-safe potrebbe non essere quello che ti aspetti.Ci sono alcuni "suggerimenti" si può vedere da:

Questa classe è completamente interoperabile con Hashtable in programmi che si basano sulla sua sicurezza thread, ma non sui suoi dettagli di sincronizzazione

Per conoscere tutta la storia in un'immagine più completa, è necessario essere a conoscenza dell'interfaccia ConcurrentMap.

L'originale Map fornisce alcuni metodi di lettura/aggiornamento molto semplici. Anche io sono stato in grado di realizzare un'implementazione thread-safe di Map; ci sono molti casi in cui le persone non possono usare la mia mappa senza considerare il mio meccanismo di sincronizzazione. Questo è un tipico esempio:

if (!threadSafeMap.containsKey(key)) { 
    threadSafeMap.put(key, value); 
} 

Questo pezzo di codice non è thread-safe, anche se la mappa stessa è. Due thread che chiamano containsKey() allo stesso tempo potrebbero pensare che non ci sia una chiave tale che entrambi quindi inseriscano nello Map.

Per risolvere il problema, è necessario eseguire la sincronizzazione aggiuntiva in modo esplicito. Suppongo che il thread-sicurezza del mio Map si ottiene parole chiave sincronizzati, è necessario fare:

synchronized(threadSafeMap) { 
    if (!threadSafeMap.containsKey(key)) { 
     threadSafeMap.put(key, value); 
    } 
} 

Tale codice aggiuntivo bisogno di voi per conoscere i "dettagli" di sincronizzazione della mappa. Nell'esempio sopra, abbiamo bisogno di sapere che la sincronizzazione è ottenuta "sincronizzata".

interfaccia ConcurrentMap fare questo un ulteriore passo avanti. Definisce alcune azioni "complesse" comuni che implicano l'accesso multiplo alla mappa. Ad esempio, l'esempio precedente è esposto come putIfAbsent(). Con queste azioni "complesse", gli utenti di ConcurrentMap (nella maggior parte dei casi) non hanno bisogno di sincronizzare le azioni con accesso multiplo alla mappa. Quindi, l'implementazione di Map può eseguire meccanismi di sincronizzazione più complicati per prestazioni migliori. ConcurrentHashhMap è un buon esempio. La sicurezza del filo viene infatti mantenuta mantenendo blocchi separati per le diverse partizioni della mappa. È thread-safe perché l'accesso simultaneo alla mappa non danneggerà la struttura interna di dati, o causare alcun aggiornamento perso inatteso, ecc

Con tutto questo in mente, il significato di Javadoc sarà più chiaro:

"Le operazioni di recupero (incluso get) generalmente non bloccano" perché ConcurrentHashMap non sta utilizzando "sincronizzato" per la sicurezza del thread. La logica di get si prende cura della sicurezza del thread; E se si guarda ulteriormente nel Javadoc:

La tabella è partizionata internamente per cercare di permettere al numero indicato aggiornamenti simultanei senza contesa

Non solo è il recupero non bloccante, anche gli aggiornamenti possono accadere contemporaneamente. Tuttavia, gli aggiornamenti non bloccanti/concomitanti non significa che sia thread-Unsafe. Significa semplicemente che sta usando alcuni modi diversi dal semplice "sincronizzato" per la sicurezza del thread.

Tuttavia, come il meccanismo di sincronizzazione interna non è esposto, se si vuole fare alcune azioni complesse diversi da quelli forniti da ConcurrentMap, potrebbe essere necessario pensare di cambiare la logica, o prendere in considerazione non utilizzare ConcurrentHashMap.Per esempio:

// only remove if both key1 and key2 exists 
if (map.containsKey(key1) && map.containsKey(key2)) { 
    map.remove(key1); 
    map.remove(key2); 
} 
2

get() in ConcurrentHashMap è thread-safe perché legge il valore che è volatile. E nei casi in cui il valore è nullo di qualsiasi chiave, il metodo get() attende fino a quando non ottiene il blocco e quindi legge il valore aggiornato .

Quando put() metodo sta aggiornando CHM, quindi imposta il valore di quella chiave a nulla, e poi si crea una nuova voce e aggiorna il CHM. Questo valore nullo viene utilizzato dal metodo get() come segnale che un altro thread sta aggiornando il CHM con la stessa chiave.