Я не думаю, что это то, что происходит в вашем случае, но можно написать тупик с одним экземпляром 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
существует сто кадров стека обратного вызова, то отследить может быть довольно сложно.