java - safe - ¿Es posible que ConcurrentHashMap "bloquee"?
thread safe hashmap (3)
Hemos encontrado un problema extraño con ConcurrentHashMap
, donde parece que dos subprocesos llaman a put()
y luego esperan para siempre dentro del método Unsafe.park()
. Desde el exterior, parece un punto muerto dentro de ConcurrentHashMap
.
Solo hemos visto que esto suceda una vez hasta ahora.
¿Alguien puede pensar en algo que pueda causar estos síntomas?
EDITAR : El volcado de hilos para los hilos relevantes está aquí:
"[redacted] Thread 2" prio=10 tid=0x000000005bbbc800 nid=0x921 waiting on condition [0x0000000040e93000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00002aaaf1207b40> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:747) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:778) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1114) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:186) at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:262) at java.util.concurrent.ConcurrentHashMap$Segment.put(ConcurrentHashMap.java:417) at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:883) at [redacted] "[redacted] Thread 0" prio=10 tid=0x000000005bf38000 nid=0x91f waiting on condition [0x000000004151d000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00002aaaf1207b40> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:747) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:778) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1114) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:186) at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:262) at java.util.concurrent.ConcurrentHashMap$Segment.put(ConcurrentHashMap.java:417) at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:883) at [redacted]
El paquete no seguro es nativo, una implementación depende de una plataforma.
La terminación abrupta del tercer subproceso (en el nivel de la plataforma, la excepción no es un problema) que adquirió un bloqueo en el mapa puede causar tal situación: el estado del bloqueo se rompe, otros dos subprocesos están deshabilitados y esperan que alguien llame a Unsafe.unpark () ( Y eso nunca sucederá.
No creo que esto sea lo que está sucediendo en su caso, pero es posible escribir un punto muerto con una sola instancia de ConcurrentHashMap
, ¡y solo se necesita un hilo! Me mantuvo atrapado durante bastante tiempo.
Supongamos que está utilizando un ConcurrentHashMap<String, Integer>
para calcular un histograma. Podrías hacer algo como esto:
int count = map.compute(key, (k, oldValue) -> {
return oldValue == null ? 1 : oldValue + 1;
});
Lo que funciona bien.
Pero digamos que en lugar de eso decides escribirlo así:
int count = map.compute(key, (k, oldValue) -> {
return map.putIfAbsent(k, 0) + 1;
});
Ahora obtendrás un interbloqueo de 1 hilo con una pila como esta:
Thread [main] (Suspended)
owns: ConcurrentHashMap$ReservationNode<K,V> (id=25)
ConcurrentHashMap<K,V>.putVal(K, V, boolean) line: not available
ConcurrentHashMap<K,V>.putIfAbsent(K, V) line: not available
ConcurrentHashMapDeadlock.lambda$0(ConcurrentHashMap, String, Integer) line: 32
1613255205.apply(Object, Object) line: not available
ConcurrentHashMap<K,V>.compute(K, BiFunction<? super K,? super V,? extends V>) line: not available
En el ejemplo anterior, es fácil ver que estamos intentando modificar el mapa dentro de una modificación atómica, lo que parece una mala idea. Sin embargo, si hay cientos de marcos de pila de devoluciones de llamada de evento entre la llamada a map.compute
y map.putIfAbsent
, entonces puede ser bastante difícil rastrearlo.
Tal vez no sea la respuesta que desea, pero esto puede ser un error de JVM. Ver