Pourquoi ConcurrentHashMap empêche-t-il les clés et valeurs nulles?

141

Le JavaDoc de ConcurrentHashMapdit ceci:

Comme Hashtablemais contrairement HashMap, cette classe ne permet pasnull d'être utilisée comme clé ou valeur.

Ma question: pourquoi?

2ème question: pourquoi n'autorise pas la valeur HashtableNULL?

J'ai utilisé beaucoup de HashMaps pour stocker des données. Mais lors du passage à, ConcurrentHashMapj'ai eu plusieurs fois des ennuis à cause de NullPointerExceptions.

Marcel
la source
1
Je pense que c'est une incohérence extrêmement ennuyeuse. EnumMap n'autorise pas non plus null. Il n'y a évidemment aucune limitation technique qui interdit les clés nulles. pour une Map <K, V>, simplement un champ de type V fournira un support pour les clés nulles (probablement un autre champ booléen si vous voulez faire la différence entre une valeur nulle et aucune valeur).
RAY
6
Une meilleure question est "pourquoi HashMap autorise-t-il une clé nulle et des valeurs nulles?". Ou peut-être, "pourquoi Java autorise-t-il null à habiter tous les types?", Ou même "pourquoi Java a-t-il des nulls?".
Jed Wesley-Smith

Réponses:

220

De l'auteur de ConcurrentHashMaplui - même (Doug Lea) :

La raison principale pour laquelle les valeurs nulles ne sont pas autorisées dans ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) est que les ambiguïtés qui peuvent être à peine tolérables dans les cartes non simultanées ne peuvent pas être prises en compte. Le principal est que si map.get(key)retourne null, vous ne pouvez pas détecter si la clé est explicitement nullmappée à vs la clé n'est pas mappée. Dans une carte non simultanée, vous pouvez vérifier cela via map.contains(key), mais dans une carte simultanée, la carte peut avoir changé entre les appels.

Bruno
la source
7
Merci, mais qu'en est-il d'avoir null comme clé?
AmitW
2
pourquoi ne pas utiliser Optionals comme valeurs en interne
benez
2
@benez Optionalest une fonctionnalité Java 8, qui n'était pas disponible à l'époque (Java 5). Vous pouvez utiliser Optionals maintenant, en effet.
Bruno
@AmitW, je pense que la réponse est la même, c'est-à-dire les ambiguïtés. Par exemple, supposons qu'un thread rend une clé nulle et stocke une valeur sur elle. Ensuite, un autre thread a changé une autre clé en null. Lorsque le deuxième thread essaie d'ajouter une nouvelle valeur, elle sera remplacée. Si le second thread essaie d'obtenir une valeur, il obtiendra la valeur d'une clé différente, celle modifiée par la première. Une telle situation doit être évitée.
Dexter
44

Je crois que c'est, au moins en partie, pour vous permettre de combiner containsKeyet geten un seul appel. Si la carte peut contenir des valeurs nulles, il n'y a aucun moyen de dire siget renvoie une valeur nulle car il n'y avait pas de clé pour cette valeur, ou simplement parce que la valeur était nulle.

Pourquoi c'est un problème? Parce qu'il n'y a pas de moyen sûr de le faire vous-même. Prenez le code suivant:

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

Puisqu'il ms'agit d'une mappe simultanée, la clé k peut être supprimée entre les appels containsKeyet get, ce qui fait que cet extrait de code renvoie une valeur null qui n'a jamais été dans la table, plutôt que celle souhaitéeKeyNotPresentException .

Normalement, vous résoudriez cela en synchronisant, mais avec une carte simultanée, cela ne fonctionnera bien sûr pas. Par conséquent, la signature pour getdevait changer, et la seule façon de le faire d'une manière rétrocompatible était d'empêcher l'utilisateur d'insérer des valeurs nulles en premier lieu, et de continuer à l'utiliser comme espace réservé pour "clé non trouvée".

Alice Purcell
la source
Vous pouvez le faire map.getOrDefault(key, NULL_MARKER). Si c'est le cas null, la valeur était null. S'il renvoie NULL_MARKER, la valeur n'était pas présente.
Oliv
@Oliv Uniquement à partir de Java 8. De plus, il se peut qu'il n'y ait pas de marqueur nul sensé pour ce type.
Alice Purcell
@AlicePurcell, "mais avec une carte concurrente qui bien sûr ne fonctionnera pas" - pourquoi, je peux synchroniser sur la version concurrente de la même manière - alors je me demande pourquoi cela ne fonctionnera pas. pouvez-vous élaborer ceci.
samshers
@samshers Aucune opération sur une carte simultanée ne se synchronise, vous devrez donc synchroniser tous les appels en externe, à quel point vous avez non seulement perdu tous les avantages de performance d'avoir une carte simultanée, vous avez laissé un piège pour les futurs responsables qui s'attendrait naturellement à pouvoir accéder en toute sécurité à une carte simultanée sans synchronisation.
Alice Purcell
@AlicePurcell, super. Bien que techniquement possible, cela va certainement être un cauchemar de maintenance et les utilisateurs ultérieurs ne s'attendront pas à ce qu'ils doivent se synchroniser sur la version simultanée.
samshers
4

Josh Bloch a conçu HashMap; Doug Lea a conçu ConcurrentHashMap. J'espère que ce n'est pas diffamatoire. En fait, je pense que le problème est que les nulls nécessitent souvent un wrapping afin que le vrai null puisse représenter non initialisé. Si le code client nécessite des valeurs nulles, il peut payer le coût (certes faible) de l'encapsulation des valeurs nulles lui-même.

Tom Hawtin - ligne de pêche
la source
2

Vous ne pouvez pas synchroniser sur une valeur nulle.

Edit: Ce n'est pas exactement pourquoi dans ce cas. J'ai d'abord pensé qu'il se passait quelque chose d'extraordinaire avec le verrouillage des choses contre les mises à jour simultanées ou l'utilisation du moniteur d'objets pour détecter si quelque chose avait été modifié, mais en examinant le code source il semble que j'avais tort - ils se verrouillent à l'aide d'un «segment» basé sur masque binaire du hachage.

Dans ce cas, je soupçonne qu'ils l'ont fait pour copier Hashtable, et je suppose que Hashtable l'a fait parce que dans le monde des bases de données relationnelles, null! = Null, donc utiliser un null comme clé n'a aucune signification.

Paul Tomblin
la source
Hein? Aucune synchronisation n'est effectuée sur les clés et les valeurs d'une carte. Cela ne ferait aucun sens.
Tobias Müller
Il existe d'autres types de verrouillage. C'est ce qui le rend "simultané". Pour ce faire, il a besoin d'un objet sur lequel s'accrocher.
Paul Tomblin
2
Pourquoi n'y a-t-il pas un objet spécifique en interne qui pourrait être utilisé pour synchroniser des valeurs nulles? par exemple "Private Object NULL = new Object ();". Je pense avoir déjà vu ça ...
Marcel
De quels autres types de verrouillage parlez-vous?
Tobias Müller
En fait, maintenant que je regarde le code source gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/ ... j'ai de sérieux doutes à ce sujet. Il semble qu'il utilise le verrouillage de segment, pas le verrouillage sur des éléments individuels.
Paul Tomblin
0

ConcurrentHashMap est thread-safe. Je crois que ne pas autoriser les clés et les valeurs nulles faisait partie de la garantie de la sécurité des threads.

Kevin Crowell
la source
0

Je suppose que l'extrait suivant de la documentation de l'API donne un bon indice: "Cette classe est entièrement interopérable avec Hashtable dans les programmes qui reposent sur sa sécurité de thread mais pas sur ses détails de synchronisation."

Ils voulaient probablement juste rendre ConcurrentHashMapentièrement compatible / interchangeable Hashtable. Et comme Hashtablen'autorise pas les clés et valeurs nulles.

Tobias Müller
la source
2
Et pourquoi Hashtable ne prend-il pas en charge null?
Marcel
En regardant son code, je ne vois pas de raison évidente pour laquelle Hashtable n'autorise pas les valeurs nulles. C'était peut-être juste une décision d'API prise à l'époque de la création de la classe?! HashMap a une gestion spéciale pour la casse nulle en interne, ce que Hashtable ne fait pas. (Il lance toujours NullPointerException.)
Tobias Müller
-2

Je ne pense pas que l'interdiction de la valeur nulle soit une option correcte. Dans de nombreux cas, nous voulons mettre une clé avec une valeur nulle dans la carte concourante. Cependant, en utilisant ConcurrentHashMap, nous ne pouvons pas faire cela. Je suggère que la prochaine version de JDK puisse prendre en charge cela.

yinhaomin
la source
1
Avez-vous pensé à concourir pour Javachampion?
BlackBishop
Utilisez Facultatif si vous souhaitez un comportement de type nul dans vos clés.
Alice Purcell