Perché ConcurrentHashMap impedisce chiavi e valori null?


141

JavaDoc di ConcurrentHashMapdice questo:

Come Hashtablema diversamente HashMap, questa classe non consente nulldi essere utilizzata come chiave o valore.

La mia domanda: perché?

2a domanda: Perché non Hashtableconsentire null?

Ho usato molte HashMaps per l'archiviazione dei dati. Ma quando ConcurrentHashMapmi sono trasformato in ho avuto diverse volte nei guai a causa di NullPointerExceptions.


1
Penso che sia un'incoerenza estremamente fastidiosa. EnumMap non consente neanche null. Ovviamente non esiste alcuna limitazione tecnica che non consente le chiavi null. per una mappa <K, V>, semplicemente un campo tipizzato a V fornirà supporto per chiavi null (probabilmente un altro campo booleano se si desidera distinguere tra valore null e nessun valore).
RAY

6
Una domanda migliore è "perché HashMap consente una chiave null e valori null?". O forse, "perché Java consente a null di abitare in tutti i tipi?", O anche "perché Java ha tutti i null?".
Jed Wesley-Smith il

Risposte:


220

Dall'autore di ConcurrentHashMapse stesso (Doug Lea) :

Il motivo principale per cui i valori null non sono consentiti in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) è che le ambiguità che possono essere appena tollerabili a malapena nelle mappe non simultanee non possono essere sistemate. Il principale è che se map.get(key)ritorna null, non è possibile rilevare se nullla chiave è mappata esplicitamente rispetto alla chiave non è mappata. In una mappa non simultanea, puoi verificarla tramite map.contains(key), ma in una mappa simultanea, la mappa potrebbe essere cambiata tra le chiamate.


7
Grazie, ma che dire di avere null come chiave?
AmitW,

2
perché non usare Optionals come valori internamente
Benez,

2
@benez Optionalè una funzionalità di Java 8, che non era disponibile allora (Java 5). Potresti usare Optionals ora, anzi.
Bruno,

@AmitW, penso che ans sia uguale, cioè ambiguità. Ad esempio, supponiamo che un thread renda una chiave come nulla e memorizzi un valore rispetto ad essa. Quindi un altro thread ha cambiato un'altra chiave in null. Quando il secondo thread tenta di aggiungere un nuovo valore, verrà sostituito. Se il secondo thread cerca di ottenere valore, otterrà il valore per una chiave diversa, quella modificata dalla prima. Tale situazione dovrebbe essere evitata.
Dexter,

44

Credo che sia, almeno in parte, per permetterti di combinare containsKeye getin una singola chiamata. Se la mappa può contenere valori null, non è possibile stabilire se getrestituisce un valore null perché non esiste una chiave per quel valore o semplicemente perché il valore era null.

Perchè questo è un problema? Perché non esiste un modo sicuro per farlo da soli. Prendi il seguente codice:

if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}

Poiché mè una mappa simultanea, la chiave k può essere eliminata tra le chiamate containsKeye get, facendo sì che questo snippet restituisca un valore null che non è mai stato nella tabella, piuttosto che quello desiderato KeyNotPresentException.

Normalmente lo risolveresti sincronizzando, ma con una mappa simultanea che ovviamente non funzionerà. Quindi la firma per getdoveva cambiare, e l'unico modo per farlo in un modo retrocompatibile era impedire all'utente di inserire valori null in primo luogo e continuare a usarlo come segnaposto per "chiave non trovata".


Si può fare map.getOrDefault(key, NULL_MARKER). Se lo è null, il valore era null. Se ritorna NULL_MARKER, il valore non era presente.
Oliv

@Oliv Solo da Java 8. Inoltre, potrebbe non esserci un marcatore nullo sensibile per quel tipo.
Alice Purcell,

@AlicePurcell, "ma con una mappa simultanea che ovviamente non funzionerà" - perché, posso sincronizzarmi sulla versione simultanea in modo simile - quindi chiedendomi perché non funzionerà. puoi elaborare questo.
samshers,

@samshers Nessuna operazione su una mappa simultanea sincronizzata, quindi dovresti sincronizzare esternamente tutte le chiamate, a quel punto non solo hai perso tutti i vantaggi prestazionali di avere una mappa concorrente, hai lasciato una trappola per i futuri manutentori che si aspetterebbe naturalmente di poter accedere in sicurezza a una mappa simultanea senza sincronizzarsi.
Alice Purcell,

@AlicePurcell, fantastico. Anche se tecnicamente possibile, sarà sicuramente un incubo per la manutenzione e non ci si aspetta che gli utenti successivi debbano sincronizzarsi con la versione concorrente.
Samshers,

4

Josh Bloch progettato HashMap; Doug Lea ha progettato ConcurrentHashMap. Spero che non sia diffamatorio. In realtà penso che il problema sia che i null spesso richiedono il wrapping in modo che il vero null possa essere considerato non inizializzato. Se il codice client richiede valori null, può pagare il costo (dichiaratamente piccolo) del wrapping dei valori null stessi.


2

Non è possibile eseguire la sincronizzazione su null.

Modifica: questo non è esattamente il motivo per cui in questo caso. Inizialmente pensavo che ci fosse qualcosa di fantasioso nel bloccare le cose contro gli aggiornamenti simultanei o altrimenti usando il monitor degli oggetti per rilevare se qualcosa fosse stato modificato, ma dopo aver esaminato il codice sorgente sembra che mi sbagliassi - si bloccano usando un "segmento" basato su un maschera di bit dell'hash.

In quel caso, sospetto che lo abbiano fatto per copiare Hashtable, e sospetto che Hashtable lo abbia fatto perché nel mondo del database relazionale, null! = Null, quindi usare un null come chiave non ha significato.


Eh? Non è stata eseguita alcuna sincronizzazione sulle chiavi e sui valori di una mappa. Non avrebbe senso.
Tobias Müller,

Esistono altri tipi di blocco. Questo è ciò che lo rende "concorrente". Per farlo, ha bisogno di un oggetto a cui aggrapparsi.
Paul Tomblin,

2
Perché non esiste un oggetto speciale internamente che possa essere utilizzato per sincronizzare i valori null? es. "Private Object NULL = new Object ();". Penso di averlo visto prima ...
Marcel il

Quali altri tipi di blocco intendi?
Tobias Müller,

In realtà, ora che guardo il codice sorgente gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/… ho seri dubbi al riguardo. Sembra che utilizza il blocco dei segmenti, non il blocco su singoli elementi.
Paul Tomblin,

0

ConcurrentHashMap è thread-safe. Ritengo che non consentire chiavi e valori null sia stato parte integrante della verifica della sicurezza dei thread.


0

Suppongo che il seguente frammento della documentazione dell'API fornisca un buon suggerimento: "Questa classe è completamente interoperabile con Hashtable in programmi che si basano sulla sicurezza del thread ma non sui dettagli di sincronizzazione".

Probabilmente volevano solo renderlo ConcurrentHashMapcompletamente compatibile / intercambiabile Hashtable. E poiché Hashtablenon consente chiavi e valori null.


2
E perché Hashtable non supporta null?
Marcel,

Osservando il suo codice, non vedo un motivo ovvio per cui Hashtable non consenta valori null. Forse era solo una decisione API da quando la classe è stata creata ?! HashMap ha una gestione speciale per il caso null internamente che Hashtable non ha. (Genera sempre NullPointerException.)
Tobias Müller,

-2

Non credo che non consentire il valore null sia un'opzione corretta. In molti casi, vogliamo inserire una chiave con valore null nella mappa con-corrente. Tuttavia, utilizzando ConcurrentHashMap, non possiamo farlo. Suggerisco che la prossima versione di JDK possa supportarlo.


1
Hai pensato di competere per Javachampion?
BlackBishop,

Utilizzare Opzionale se si desidera un comportamento nullo nelle chiavi.
Alice Purcell,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.