Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible for ConcurrentHashMap to "deadlock"?

Tags:

We have come across a strange issue with ConcurrentHashMap, where two threads appears to be calling put(), and then waiting forever inside the method Unsafe.park(). From the outside, it looks like a deadlock inside ConcurrentHashMap.

We have only seen this happen once so far.

Can anyone think of anything that could cause these symptoms?

EDIT: The thread dump for the relevant threads is here:

  "[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] 
like image 471
Simon Nickerson Avatar asked Jul 20 '10 17:07

Simon Nickerson


People also ask

Does ConcurrentHashMap use locks?

Yes, ConcurrentHashMap uses a multitude of locks (by default, 16 of them), each lock controls one segment of the hash. When setting data in a particular segment, the lock for that segment is obtained. When getting data, a volatile read is used.

How does locking happen in ConcurrentHashMap?

In ConcurrentHashMap, at a time any number of threads can perform retrieval operation but for updated in the object, the thread must lock the particular segment in which the thread wants to operate. This type of locking mechanism is known as Segment locking or bucket locking.

Is ConcurrentHashMap thread-safe?

ConcurrentHashMap class achieves thread-safety by dividing the map into segments, the lock is required not for the entire object but for one segment, i.e one thread requires a lock of one segment.

Does ConcurrentHashMap need to be volatile?

No, you don't. volatile means that the variable cannot be cached in a register, and so will always be "write-through" to memory. This means that one thread's change to a variable will be visible to other threads. In this case, the variable is a reference to a Map.


1 Answers

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.

like image 141
Ned Twigg Avatar answered Sep 22 '22 16:09

Ned Twigg