I don't think this is what's happening in your case, but it is possible to write a deadlock with a single instance of ConcurrentHashMap
, and it only needs one thread! Kept me stuck for quite a while.
Let's say you're using a ConcurrentHashMap<String, Integer>
to calculate a histogram. You might do something like this:
int count = map.compute(key, (k, oldValue) -> {
return oldValue == null ? 1 : oldValue + 1;
});
Which works just fine.
But let's say you decide instead to write it like this:
int count = map.compute(key, (k, oldValue) -> {
return map.putIfAbsent(k, 0) + 1;
});
You will now get a 1-thread deadlock with a stack like this:
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
In the example above, it's easy to see that we're attempting to modify the map inside of an atomic modification, which seems like a bad idea. However, if there are a hundred stack frames of event-callbacks between the call to map.compute
and map.putIfAbsent
, then it can be quite difficult to track down.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…