java - example - concurrenthashmap ejemplo
Entendiendo el código del método de cálculo ConcurrentHashMap (3)
Acabo de encontrar este código extraño en el método de cálculo ConcurrentHashMap: (línea 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;
Por lo tanto, el código realiza la sincronización en una nueva variable que está disponible solo para el hilo actual. Eso significa que no hay otro hilo para competir por este bloqueo o para causar efectos de bloque de memoria.
¿Cuál es el punto de esta acción? ¿Es un error o causa algunos efectos secundarios no obvios que no conozco?
ps jdk1.8.0_131
Si bien r
es una nueva variable en ese punto, se coloca en la table
interna inmediatamente a través de if (casTabAt(tab, i, null, r))
punto en el cual otro hilo puede acceder a ella en diferentes partes del código.
Un comentario interno no javadoc lo describe así.
La inserción (a través de put o sus variantes) del primer nodo en un contenedor vacío se realiza simplemente CASEANDO al contenedor. Este es, con mucho, el caso más común para operaciones de colocación en la mayoría de las distribuciones de clave / hash. Otras operaciones de actualización (insertar, eliminar y reemplazar) requieren bloqueos. No queremos desperdiciar el espacio requerido para asociar un objeto de bloqueo distinto con cada bandeja, por lo que en su lugar, utilice el primer nodo de una lista de la caja como un bloqueo. El soporte de bloqueo para estos bloqueos se basa en monitores "sincronizados" integrados.
Solo 0.02 $ aquí
Lo que se muestra allí es en realidad solo el ReservationNode
, lo que significa que el contenedor está vacío y que se hace una reserva de algunos Nodos. Tenga en cuenta que este método reemplaza este Nodo con uno real:
setTabAt(tab, i, node);
Así que esto se hace para que el reemplazo sea atómico, por lo que yo entiendo. Una vez publicados a través de casTabAt
y si otros subprocesos lo ven, no pueden sincronizarse ya que el bloqueo ya está retenido.
También tenga en cuenta que cuando hay una entrada en un contenedor, ese primer nodo se usa para sincronizar (está más abajo en el método):
boolean added = false;
synchronized (f) { // locks the bin on the first Node
if (tabAt(tab, i) == f) {
......
Como nodo lateral, este método ha cambiado en 9
, desde 8
. Por ejemplo ejecutando este código:
map.computeIfAbsent("KEY", s -> {
map.computeIfAbsent("KEY"), s -> {
return 2;
}
})
nunca terminaría en 8, pero lanzaría una Recursive Update
en 9.
casTabAt(tab, i, null, r)
Está publicando la referencia a 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);
}
Debido a que c
se está colocando en la tab
, es posible que se acceda a él por otro hilo, por ejemplo, en putVal
. Como tal, este bloque synchronized
es necesario para excluir a otros subprocesos de hacer otras cosas sincronizadas con ese Node
.