Se ho due thread multipli che accedono a una HashMap, ma garantisco che non accederanno mai alla stessa chiave contemporaneamente, ciò potrebbe comunque portare a una condizione di competizione?
Se ho due thread multipli che accedono a una HashMap, ma garantisco che non accederanno mai alla stessa chiave contemporaneamente, ciò potrebbe comunque portare a una condizione di competizione?
Risposte:
Nella risposta di @ dotsid dice questo:
Se modifichi una HashMap in qualsiasi modo, il tuo codice è semplicemente rotto.
Ha ragione. Una HashMap aggiornata senza sincronizzazione si interromperà anche se i thread utilizzano set di chiavi disgiunti. Ecco alcune delle cose che possono andare storte.
Se un thread esegue un put
, un altro thread potrebbe visualizzare un valore non aggiornato per la dimensione della hashmap.
Quando un thread esegue un'operazione put
che attiva una ricostruzione della tabella, un altro thread può visualizzare versioni transitorie o obsolete del riferimento all'array hashtable, le sue dimensioni, i suoi contenuti o le catene hash. Potrebbe derivarne il caos.
Quando un thread esegue una put
per una chiave che si scontra con una chiave utilizzata da un altro thread e quest'ultimo thread esegue una put
per la sua chiave, quest'ultimo potrebbe vedere una copia obsoleta del riferimento alla catena hash. Potrebbe derivarne il caos.
Quando un thread sonda la tabella con una chiave che entra in collisione con una delle chiavi di un altro thread, potrebbe incontrare quella chiave sulla catena. Chiamerà uguale su quella chiave e, se i thread non sono sincronizzati, il metodo equals potrebbe riscontrare uno stato non aggiornato in quella chiave.
E se hai due thread che eseguono put
o remove
richieste contemporaneamente , ci sono numerose opportunità per le condizioni di gara.
Posso pensare a tre soluzioni:
ConcurrentHashMap
.HashMap
ma sincronizzato all'esterno; es. usando mutex primitivi, Lock
oggetti, eccetera.HashMap
per ogni thread. Se i thread hanno davvero un set di chiavi disgiunto, non dovrebbe essere necessario (dal punto di vista algoritmico) che condividano una singola mappa. In effetti, se i tuoi algoritmi coinvolgono i thread che iterano le chiavi, i valori o le voci della mappa a un certo punto, la divisione della singola mappa in più mappe potrebbe dare una velocità significativa per quella parte dell'elaborazione.Usa solo un ConcurrentHashMap. ConcurrentHashMap utilizza più blocchi che coprono una gamma di bucket di hash per ridurre le possibilità che un blocco venga contestato. Esiste un impatto marginale sulle prestazioni nell'acquisizione di un blocco non contestato.
Per rispondere alla tua domanda originale: secondo il javadoc, finché la struttura della mappa non cambia, stai bene. Ciò significa nessuna rimozione di elementi e nessuna aggiunta di nuove chiavi che non sono già nella mappa. La sostituzione del valore associato alle chiavi esistenti va bene.
Se più thread accedono contemporaneamente a una mappa hash e almeno uno dei thread modifica strutturalmente la mappa, è necessario sincronizzarla esternamente. (Una modifica strutturale è qualsiasi operazione che aggiunge o elimina una o più mappature; la semplice modifica del valore associato a una chiave già contenuta in un'istanza non è una modifica strutturale.)
Anche se non garantisce la visibilità. Quindi devi essere disposto ad accettare il recupero di associazioni obsolete di tanto in tanto.
Dipende da cosa intendi per "accesso". Se stai leggendo, puoi leggere anche le stesse chiavi purché la visibilità dei dati sia garantita secondo le regole " accade prima ". Ciò significa che HashMap
non dovrebbe cambiare e tutte le modifiche (costruzioni iniziali) dovrebbero essere completate prima che qualsiasi lettore inizi ad accedere HashMap
.
Se modifichi un HashMap
in qualsiasi modo, il tuo codice è semplicemente rotto. @Stephen C fornisce un'ottima spiegazione del perché.
EDIT: Se il primo caso è la tua situazione attuale, ti consiglio di usarlo Collections.unmodifiableMap()
per assicurarti che la tua HashMap non venga mai modificata. Anche gli oggetti puntati da HashMap
non dovrebbero cambiare, quindi l'uso aggressivo di final
parole chiave può aiutarti.
E come dice @Lars Andren, ConcurrentHashMap
è la scelta migliore nella maggior parte dei casi.
unmodifiableMap
assicura che il client non possa modificare la mappa. Non fa nulla per garantire che la mappa sottostante non venga modificata.
La modifica di una HashMap senza una corretta sincronizzazione da due thread può facilmente portare a una condizione di competizione.
put()
porta a un ridimensionamento della tabella interna, questo richiede del tempo e l'altro thread continua a scrivere nella vecchia tabella.put()
per chiavi diverse portano ad un aggiornamento dello stesso bucket se i codici hash delle chiavi sono uguali al modulo della dimensione della tabella. (In realtà, la relazione tra codice hash e indice del bucket è più complicata, ma possono comunque verificarsi collisioni.)HashMap
dell'implementazione che stai utilizzando, puoi ottenere il danneggiamento delle HashMap
strutture dati, eccetera a causa di anomalie della memoria.