Warum verhindert ConcurrentHashMap Nullschlüssel und -werte?

141

Das JavaDoc von ConcurrentHashMapsagt dies:

Wie, Hashtableaber nicht, erlaubt HashMapdiese Klasse nicht , nullals Schlüssel oder Wert verwendet zu werden.

Meine Frage: Warum?

2. Frage: Warum nicht Hashtablenull zulassen?

Ich habe viele HashMaps zum Speichern von Daten verwendet. Aber als ConcurrentHashMapich zu wechselte, bekam ich wegen NullPointerExceptions mehrmals Probleme.

Marcel
quelle
1
Ich denke, es ist eine äußerst ärgerliche Inkonsistenz. EnumMap erlaubt auch keine Null. Es gibt offensichtlich keine technische Einschränkung, die Nullschlüssel nicht zulässt. Für eine Map <K, V> bietet einfach ein V-typisiertes Feld Unterstützung für Nullschlüssel (wahrscheinlich ein weiteres boolesches Feld, wenn Sie zwischen Nullwert und keinem Wert unterscheiden möchten).
RAY
6
Eine bessere Frage ist "Warum erlaubt HashMap einen Nullschlüssel und Nullwerte?". Oder möglicherweise "Warum erlaubt Java Null, alle Typen zu bewohnen?" Oder sogar "Warum hat Java überhaupt Nullen?".
Jed Wesley-Smith

Antworten:

220

Vom Autor seiner ConcurrentHashMapselbst (Doug Lea) :

Der Hauptgrund dafür, dass Nullen in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) nicht zulässig sind, besteht darin, dass Mehrdeutigkeiten, die in nicht gleichzeitigen Maps möglicherweise kaum tolerierbar sind, nicht berücksichtigt werden können. Das wichtigste ist, dass Sie bei einer map.get(key)Rückkehr nullnicht erkennen können, ob der Schlüssel explizit nulldem Schlüssel zugeordnet ist oder nicht. In einer nicht gleichzeitigen Karte können Sie dies über überprüfen map.contains(key), in einer gleichzeitigen Karte hat sich die Karte möglicherweise zwischen den Aufrufen geändert.

Bruno
quelle
7
Vielen Dank, aber was ist mit null als Schlüssel?
AmitW
2
warum nicht Optionals als interne Werte verwenden
benez
2
@benez Optionalist eine Java 8-Funktion, die damals noch nicht verfügbar war (Java 5). Sie könnten Optionaljetzt tatsächlich s verwenden.
Bruno
@AmitW, ich denke, das Ans ist das gleiche, dh Mehrdeutigkeiten. Angenommen, ein Thread erstellt einen Schlüssel als null und speichert einen Wert dafür. Dann änderte ein anderer Thread einen anderen Schlüssel in null. Wenn der zweite Thread versucht, einen neuen Wert hinzuzufügen, wird dieser ersetzt. Wenn der zweite Thread versucht, einen Wert zu erhalten, erhält er den Wert für einen anderen Schlüssel, den vom ersten modifizierten. Eine solche Situation sollte vermieden werden.
Dexter
44

Ich glaube , es ist zumindest teilweise zu erlauben , Sie zu kombinieren containsKeyund getzu einem einzigen Anruf. Wenn die Map Nullen enthalten kann, kann nicht festgestellt werden, ob geteine Null zurückgegeben wird, weil für diesen Wert kein Schlüssel vorhanden war oder nur weil der Wert null war.

Warum ist das ein Problem? Weil es keinen sicheren Weg gibt, das selbst zu tun. Nehmen Sie den folgenden Code:

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

Da mes sich um eine gleichzeitige Zuordnung handelt, kann der Schlüssel k zwischen den Aufrufen containsKeyund gelöscht werden get, wodurch dieses Snippet eine Null zurückgibt, die nie in der Tabelle enthalten war, und nicht die gewünschte KeyNotPresentException.

Normalerweise würden Sie das durch Synchronisieren lösen, aber mit einer gleichzeitigen Karte funktioniert das natürlich nicht. Daher musste sich die Signatur für getändern, und die einzige Möglichkeit, dies abwärtskompatibel zu tun, bestand darin, zu verhindern, dass der Benutzer überhaupt Nullwerte einfügt, und diese weiterhin als Platzhalter für "Schlüssel nicht gefunden" zu verwenden.

Alice Purcell
quelle
Das kannst du machen map.getOrDefault(key, NULL_MARKER). Wenn ja null, war der Wert null. Wenn es zurückgegeben wird NULL_MARKER, war der Wert nicht vorhanden.
Oliv
@Oliv Nur ab Java 8. Außerdem gibt es möglicherweise keinen sinnvollen Nullmarker für diesen Typ.
Alice Purcell
@AlicePurcell, "aber mit einer gleichzeitigen Karte, die natürlich nicht funktioniert" - warum kann ich auf die gleichzeitige Version ähnlich synchronisieren - also frage ich mich, warum es nicht funktioniert. Können Sie das näher erläutern?
Samshers
@samshers Keine Vorgänge auf einer gleichzeitigen Zuordnung werden synchronisiert, sodass Sie alle Aufrufe extern synchronisieren müssen. Zu diesem Zeitpunkt haben Sie nicht nur den Leistungsvorteil einer gleichzeitigen Zuordnung verloren, sondern auch eine Falle für zukünftige Betreuer hinterlassen Ich würde natürlich erwarten, dass ich ohne Synchronisierung sicher auf eine gleichzeitige Karte zugreifen kann.
Alice Purcell
@ AlicePurcell, großartig. Obwohl dies technisch möglich ist, wird dies definitiv ein Wartungs-Albtraum sein und es wird von späteren Benutzern nicht erwartet, dass sie mit der gleichzeitigen Version synchronisieren müssen.
Samshers
4

Josh Bloch entworfen HashMap; Doug Lea entworfen ConcurrentHashMap. Ich hoffe das ist nicht verleumderisch. Eigentlich denke ich, dass das Problem darin besteht, dass Nullen oft umbrochen werden müssen, damit die echte Null für nicht initialisiert stehen kann. Wenn für den Client-Code Nullen erforderlich sind, kann er die (zugegebenermaßen geringen) Kosten für das Umschließen von Nullen selbst bezahlen.

Tom Hawtin - Tackline
quelle
2

Sie können nicht mit einer Null synchronisieren.

Bearbeiten: Dies ist in diesem Fall nicht genau der Grund. Anfangs dachte ich, es wäre etwas Besonderes, Dinge gegen gleichzeitige Aktualisierungen zu sperren oder den Objektmonitor auf andere Weise zu verwenden, um festzustellen, ob etwas geändert wurde, aber bei der Prüfung des Quellcodes stellte sich heraus, dass ich falsch lag - sie sperren mit einem "Segment", das auf a basiert Bitmaske des Hash.

In diesem Fall vermute ich, dass sie es getan haben, um Hashtable zu kopieren, und ich vermute, dass Hashtable es getan hat, weil in der relationalen Datenbankwelt null! = Null ist, sodass die Verwendung einer Null als Schlüssel keine Bedeutung hat.

Paul Tomblin
quelle
Huh? Die Schlüssel und Werte einer Karte werden nicht synchronisiert. Das würde keinen Sinn ergeben.
Tobias Müller
Es gibt andere Arten von Sperren. Das macht es "Concurrent". Dazu benötigt es ein Objekt zum Festhalten.
Paul Tomblin
2
Warum gibt es intern kein bestimmtes Objekt, das zum Synchronisieren von Nullwerten verwendet werden könnte? zB "privates Objekt NULL = neues Objekt ();". Ich glaube, ich habe das schon einmal gesehen ...
Marcel
Welche anderen Arten von Sperren meinen Sie?
Tobias Müller
Jetzt, wo ich mir den Quellcode gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/ anschaue, habe ich ernsthafte Zweifel daran. Es scheint, dass die Segment-Sperrung verwendet wird, nicht die Sperrung einzelner Elemente.
Paul Tomblin
0

ConcurrentHashMap ist threadsicher. Ich glaube, dass das Nichtzulassen von Nullschlüsseln und -werten dazu beigetragen hat, sicherzustellen, dass es threadsicher ist.

Kevin Crowell
quelle
0

Ich vermute, dass der folgende Ausschnitt der API-Dokumentation einen guten Hinweis gibt: "Diese Klasse ist in Programmen, die auf der Thread-Sicherheit, aber nicht auf den Synchronisationsdetails beruhen, vollständig mit Hashtable kompatibel."

Sie wollten wahrscheinlich nur ConcurrentHashMapvoll kompatibel / austauschbar machen Hashtable. Und da Hashtableerlaubt keine Nullschlüssel und Werte ..

Tobias Müller
quelle
2
Und warum unterstützt Hashtable null nicht?
Marcel
Wenn ich mir den Code anschaue, sehe ich keinen offensichtlichen Grund, warum Hashtable keine Nullwerte zulässt. Vielleicht war es nur eine API-Entscheidung von damals, als die Klasse erstellt wurde?! HashMap hat eine spezielle Behandlung für den internen Nullfall, die Hashtable nicht tut. (Es wird immer eine NullPointerException ausgelöst.)
Tobias Müller
-2

Ich denke nicht, dass es eine richtige Option ist, den Nullwert nicht zuzulassen. In vielen Fällen möchten wir einen Schlüssel mit dem Wert Null in die aktuelle Karte einfügen. Mit ConcurrentHashMap können wir dies jedoch nicht tun. Ich schlage vor, dass die kommende Version von JDK dies unterstützen kann.

Yinhaomin
quelle
1
Haben Sie darüber nachgedacht, um Javachampion zu kämpfen?
BlackBishop
Verwenden Sie Optional, wenn Sie ein nullähnliches Verhalten in Ihren Schlüsseln wünschen.
Alice Purcell