HashMap è un thread sicuro per chiavi diverse?

Se ho due thread multipli che accedono a una HashMap, ma garantisco che non accederanno mai alla stessa chiave nello stesso momento, potrebbe comunque portare a una condizione di competizione?

Nella risposta di @ dotsid dice questo:

Se cambi una HashMap in qualche modo, il tuo codice sarà semplicemente rotto.

Lui ha ragione. Una HashMap che viene aggiornata senza sincronizzazione si interrompe anche se i thread utilizzano set di chiavi disgiunti. Ecco alcune delle cose che possono andare storte.

  • Se un thread fa un put , allora un altro thread potrebbe vedere un valore scaduto per le dimensioni dell’hashmap.

  • Quando un thread esegue una put che triggers una ricostruzione della tabella, un altro thread può visualizzare versioni transitorie o obsolete del riferimento dell’array hashtable, delle sue dimensioni, dei suoi contenuti o delle catene hash. Il caos potrebbe derivarne.

  • Quando un thread fa un put per una chiave che si scontra con una chiave usata da qualche altro thread, e quest’ultimo thread fa un put per la sua chiave, allora quest’ultimo potrebbe vedere una copia obsoleta del riferimento alla catena di hash. Il caos potrebbe derivarne.

  • Quando un thread analizza la tabella con una chiave che si scontra con una delle chiavi di un altro thread, potrebbe incontrare quella chiave sulla catena. Chiamerà equals su quella chiave e se i thread non sono sincronizzati, il metodo equals potrebbe incontrare lo stato stantio in quella chiave.

E se hai due thread contemporaneamente che fanno richieste di remove o di remove , ci sono numerose opportunità per le condizioni di gara.

Posso pensare a tre soluzioni:

  1. Usa una ConcurrentHashMap .
  2. Usa una normale HashMap ma sincronizza all’esterno; es. usando mutex primitivi, Lock objects, eccetera.
  3. Usa una HashMap diversa per ogni thread. Se i thread hanno davvero un insieme disgiunto di chiavi, non dovrebbe esserci alcuna necessità (da una prospettiva algoritmica) di condividere una singola mappa. Infatti, se i tuoi algoritmi coinvolgono i thread che iterano le chiavi, i valori o le voci della mappa a un certo punto, suddividere la singola mappa in più mappe potrebbe dare una significativa accelerazione per quella parte dell’elaborazione.

Basta usare una ConcurrentHashMap. ConcurrentHashMap utilizza più blocchi che coprono un intervallo di bucket hash per ridurre le possibilità di un blocco contestato. C’è un impatto marginale sulle prestazioni nell’acquisire un blocco non contestato.

Per rispondere alla tua domanda iniziale: secondo javadoc, finché la struttura della mappa non cambia, stai bene. Questo significa che non ci sono elementi di rimozione e che non si aggiungono nuove chiavi che non sono già nella mappa. Sostituire il valore associato alle chiavi esistenti va bene.

Se più thread accedono contemporaneamente a una mappa hash e almeno uno dei thread modifica la mappa in modo strutturale, deve essere sincronizzato esternamente. (Una modifica strutturale è qualsiasi operazione che aggiunge o elimina uno o più mapping, semplicemente cambiando il valore associato a una chiave che un’istanza contiene già non è una modifica strutturale.)

Sebbene non offra garanzie sulla visibilità. Quindi devi essere disposto ad accettare il recupero di associazioni stantie occasionalmente.

Dipende da cosa intendi con “accesso”. Se stai leggendo, puoi leggere anche le stesse chiavi finché la visibilità dei dati è garantita dalle regole ” succede prima “. Ciò significa che HashMap non dovrebbe cambiare e tutte le modifiche (costruzioni iniziali) dovrebbero essere completate prima che qualsiasi lettore inizi ad accedere a HashMap .

Se cambi una HashMap in qualche modo, il tuo codice sarà semplicemente rotto. @Stephen C fornisce un’ottima spiegazione del perché.

EDIT: Se il primo caso è la tua situazione attuale, ti consiglio di usare Collections.unmodifiableMap() per essere sicuro che la tua HashMap non venga mai cambiata. Gli oggetti puntati da HashMap non dovrebbero cambiare anche, in modo aggressivo usando la parola chiave final può aiutarti.

E come dice @Lars Andren, ConcurrentHashMap è la scelta migliore nella maggior parte dei casi.

La modifica di una HashMap senza sincronizzazione corretta da due thread può facilmente portare a una condizione di competizione.

  • Quando un put() porta a un ridimensionamento della tabella interna, questo richiede un po ‘di tempo e l’altro thread continua a scrivere sulla vecchia tabella.
  • Due put() per chiavi diverse portano ad un aggiornamento dello stesso bucket se i codici hash delle chiavi sono uguali modulo le dimensioni della tabella. (In realtà, la relazione tra hashcode e bucket index è più complicata, ma possono ancora verificarsi collisioni.)