Tout d'abord, il ne manque pas de manière fiable. J'ai réussi à avoir quelques pistes où aucune exception s'est produite. Ceci, cependant, ne signifie pas que la carte est correcte. Il est également possible que chaque thread témoins de sa propre valeur avec succès, tandis que la carte qui manque plusieurs mappages.
Mais en effet, à défaut avec une NullPointerException
arrive assez souvent. J'ai créé le code de débogage pour illustrer l' HashMap
de travail de:
static <K,V> void debugPut(HashMap<K,V> m, K k, V v) {
if(m.isEmpty()) debug(m);
m.put(k, v);
debug(m);
}
private static <K, V> void debug(HashMap<K, V> m) {
for(Field f: FIELDS) try {
System.out.println(f.getName()+": "+f.get(m));
} catch(ReflectiveOperationException ex) {
throw new AssertionError(ex);
}
System.out.println();
}
static final Field[] FIELDS;
static {
String[] name={ "table", "size", "threshold" };
Field[] f=new Field[name.length];
for (int ix = 0; ix < name.length; ix++) try {
f[ix]=HashMap.class.getDeclaredField(name[ix]);
}
catch (NoSuchFieldException ex) {
throw new ExceptionInInitializerError(ex);
}
AccessibleObject.setAccessible(f, true);
FIELDS=f;
}
L'utilisation de ce avec la simple séquentielle for(int i=0; i<5; i++) debugPut(m, i, i);
imprimé:
table: null
size: 0
threshold: 1
table: [Ljava.util.HashMap$Node;@70dea4e
size: 1
threshold: 1
table: [Ljava.util.HashMap$Node;@5c647e05
size: 2
threshold: 3
table: [Ljava.util.HashMap$Node;@5c647e05
size: 3
threshold: 3
table: [Ljava.util.HashMap$Node;@33909752
size: 4
threshold: 6
table: [Ljava.util.HashMap$Node;@33909752
size: 5
threshold: 6
Comme vous pouvez le voir, en raison de la capacité initiale de l' 0
, il existe trois types de sauvegarde des tableaux créés, même pendant le fonctionnement séquentiel. À chaque fois, la capacité est augmentée, il ya une chance plus élevée que racé, d'un simultanées put
manque le tableau de mise à jour et crée son propre tableau.
Cela est particulièrement pertinent pour l'état initial d'une carte vide et plusieurs threads tentent de mettre leur première clé, comme tous les threads peut rencontrer l'état initial d'un null
tableau et de créer leurs propres. Aussi, même lors de la lecture de l'état d'un premier put
, il y a un nouveau tableau créé pour le deuxième put
ainsi.
Mais l'étape-par-étape de débogage révélé encore plus de chances de rupture:
À l'intérieur de la méthode de putVal
, nous voyons à la fin:
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
En d'autres termes, après la réussite de l'insertion d'une nouvelle clé, le tableau sera redimensionnée, si la nouvelle taille dépasse l' threshold
. Ainsi, sur la première put
, resize()
est appelé au début, parce que le tableau est null
et depuis votre capacité initiale est de 0
, c'est à dire trop faible pour stocker une cartographie, de la nouvelle capacité sera 1
et la nouvelle - threshold
sera 1 * loadFactor == 1 * 0.75f == 0.75f
, arrondi à l' 0
. Donc, à la fin de la première put
, la nouvelle - threshold
est dépassé et un autre resize()
opération déclenchée. Donc, avec une capacité initiale de 0
, la première put
déjà crée et remplit deux tableaux, ce qui donne beaucoup plus de chances de briser, si plusieurs threads d'exécuter cette action en parallèle, tout en rencontrant l'état initial.
Et il y a un autre point. La recherche dans l' resize()
de l'opération , nous voyons les lignes:
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
… (transfer old contents to new array)
En d'autres termes, le nouveau tableau de référence est stockée dans le tas avant , il a été rempli avec les anciennes entrées, de sorte que même sans réorganisation de lecture et d'écriture, il y a une chance qu'un autre thread qui lit de référence sans voir les anciennes entrées, y compris celui qu'il a écrit lui-même auparavant. En fait, les optimisations de réduire le tas d'accès peuvent réduire les chances d'un fil de ne pas voir sa propre mise à jour dans une immédiatement après la requête.
Encore, il convient également de noter que l'hypothèse que tout fonctionne interprétée ici, n'est pas fondé. Depuis HashMap
est utilisé par le JRE aussi bien à l'interne, avant même de votre application démarre, il y a aussi une chance de rencontrer déjà le code compilé lors de l'utilisation d' HashMap
.