Je viens de trouver cet étrange code dans la méthode de calcul ConcurrentHashMap: (ligne 1847)
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
...
Node<K,V> r = new ReservationNode<K,V>();
synchronized (r) { <--- what is this?
if (casTabAt(tab, i, null, r)) {
binCount = 1;
Node<K,V> node = null;
Ainsi, le code effectue la synchronisation sur une nouvelle variable qui n'est disponible que pour le thread actuel. Cela signifie qu'il n'y a pas d'autre thread pour rivaliser pour ce verrou ou pour provoquer des effets de barrages de mémoire.
Quel est l'intérêt de cette action? Est-ce une erreur ou cela provoque des effets secondaires non évidents dont je ne suis pas au courant?
p.s. jdk1.8.0_131
casTabAt(tab, i, null, r)
publie la référence à r
.
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
Étant donné que c
est placé dans tab
, il est possible qu'il soit accessible par un autre thread, par exemple dans putVal
. En tant que tel, ce bloc synchronized
est nécessaire pour empêcher d'autres threads de faire d'autres choses synchronisées avec ce Node
.
Alors que r
est une nouvelle variable à ce stade, elle est placée dans le table
interne immédiatement par le biais de if (casTabAt(tab, i, null, r))
auquel point un autre thread peut y accéder dans différentes parties de le code.
Un commentaire interne non javadoc le décrit ainsi
L'insertion (via put ou ses variantes) du premier nœud dans un bac vide est effectuée en le plaçant simplement dans le bac. C'est de loin le cas le plus courant pour les opérations de placement sous la plupart des distributions de clés/hachage. D'autres opérations de mise à jour (insertion, suppression et remplacement) nécessitent des verrous. Nous ne voulons pas gaspiller l'espace requis pour associer un objet de verrouillage distinct à chaque casier, alors utilisez plutôt le premier nœud d'une liste de casiers lui-même comme verrou. La prise en charge du verrouillage de ces verrous repose sur des moniteurs "synchronisés" intégrés.
Juste 0,02 $ ici
Ce que vous avez montré là est en fait juste le ReservationNode
- ce qui signifie que le bac est vide et qu'un réservation de certains Node est fait. Notez que cette méthode remplace plus tard ceci Node avec un vrai:
setTabAt(tab, i, node);
Donc, cela est fait pour que le remplacement soit atomique pour autant que je sache. Une fois publié via casTabAt
et si d'autres threads le voient - ils ne peuvent pas se synchroniser dessus car le verrou est déjà maintenu.
Notez également que lorsqu'il y a une entrée dans un bac, ce premier Node est utilisé pour se synchroniser (il est plus bas dans la méthode):
boolean added = false;
synchronized (f) { // locks the bin on the first Node
if (tabAt(tab, i) == f) {
......
En tant que nœud latéral, cette méthode a changé dans 9
, puisque 8
. Par exemple, exécuter ce code:
map.computeIfAbsent("KEY", s -> {
map.computeIfAbsent("KEY"), s -> {
return 2;
}
})
ne finirait jamais en 8, mais jetterait un Recursive Update
en 9.