Возможно ли для ConcurrentHashMap"тупик"?


Мы столкнулись со странной проблемой с ConcurrentHashMap, где два потока, кажется, вызывают put(), а затем ждут вечно внутри метода Unsafe.park(). Снаружи это выглядит как тупик внутри ConcurrentHashMap.

До сих пор мы видели это только один раз. Может ли кто-нибудь придумать что-нибудь, что могло бы вызвать эти симптомы?

EDIT : дамп потока для соответствующих потоков находится здесь:


"[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]
3 21

3 ответа:

Возможно, это не тот ответ, который вам нужен, Но это может быть ошибка JVM. См.

Http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6865591

Я не думаю, что это то, что происходит в вашем случае, но можно написать тупик с одним экземпляром ConcurrentHashMap, и для этого нужен только один поток! Я застрял там на довольно долгое время.

Предположим, вы используете ConcurrentHashMap<String, Integer> для вычисления гистограммы. Вы можете сделать что-то вроде этого:
int count = map.compute(key, (k, oldValue) -> {
    return oldValue == null ? 1 : oldValue + 1;
});

Который работает просто отлично.

Но предположим, что вместо этого вы решите написать его так:

int count = map.compute(key, (k, oldValue) -> {
    return map.putIfAbsent(k, 0) + 1;
});

Теперь вы получите 1-нить взаимоблокировки со стеком, как это:

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  
В приведенном выше примере легко увидеть, что мы пытаемся изменить карту внутри атомной модификации, что кажется плохой идеей. Однако если между вызовами map.compute и map.putIfAbsent имеется сотня стековых кадров событий-обратных вызовов, то их довольно трудно отследить.

Пакет Unsafe является нативным, реализация зависит от платформы.

Внезапное завершение третьего потока (на уровне платформы, exception не является проблемой), который получил блокировку на карте, может вызвать такую ситуацию - состояние блокировки нарушено, два других потока отключены и ждут, когда кто-то вызовет небезопасный.unpark () (и этого никогда не произойдет).