In an interview, I was asked to check whether following code works as intended.
ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<>();
if (chm.get(key) != null) {
chm.get(key).doSomething();
chm.remove(key);
}
According to JavaDocs, get
returns value of last completed update operation. So if thread 1 already called chm.remove(key)
and if thread 2 came inside the if statement and is about to call get
method then we might get an exception. Is this correct?
How can I make this thread-safe?
ConcurrentHashMap class is thread-safe i.e. multiple threads can operate on a single object without any complications.
The remove(Object key) method of class ConcurrentHashmap in Java is used to remove the mapping from the map. If the key does not exist in the map, then this function does nothing. Return Value: This method returns the previous value associated with key, or null if there was no mapping for key.
ConcurrentHashMap: It allows concurrent access to the map. Part of the map called Segment (internal data structure) is only getting locked while adding or updating the map. So ConcurrentHashMap allows concurrent threads to read the value without locking at all.
keySet() is thread safe. However, it may act in very strange ways, as pointed out in the quote you included. As a Set , entries may appear and/or disappear at random. I.e. if you call contains twice on the same object, the two results may differ.
Map.remove(key)
returns the value if it has been removed. This is a very good trick in many situations, including yours:
Object value = chm.remove(key)
if(value != null)
{
value.doSomething();
}
You can't work safely with a get then a remove because if two threads call your method at the same time, there's always a risk they call doSomething
two or more times, before the key has been removed.
This is not possible if you remove it first. The code above is Threadsafe, also simpler.
You are correct. If this Map
can be modified by multiple threads, it's possible that the first call to chm.get(key)
will return a non-null value and the second call will return null
(due to the removal of the key from the Map
done by another thread), and thus chm.get(key).doSomething()
will throw a NullPointerException
.
You can make this code thread safe by using the local variable to store the result of chm.get(key)
:
ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<Integer, Integer>();
Integer value = chm.get(key);
if(value != null) {
value.doSomething(); // P.S. Integer class doesn't have a doSomething() method
// but I guess this is just an example of calling some arbitrary
// instance method
chm.remove(key);
}
BTW, even if that Map
wasn't a ConcurentHashMap
and only one thread had access to it, I'd still use a local variable, since it's more efficient than calling the get()
method twice.
EDIT :
As commented below, this fix will not prevent doSomething()
from being called multiple times for the same key/value by different threads. Whether or not that's the desired behavior is not clear.
If you wish to prevent the possiblity of doSomething()
being called by multiple threads for the same key/value, you can use chm.remove(key)
for both removing the key and obtaining the value at the same step.
This however runs the risk that doSomething()
will not be executed at all for some key/value, since if the first call to doSomething()
resulted in an exception, there won't be another call to doSomething()
by another thread, since the key/value pair will no longer be in the Map
. On the other hand, if you remove the key/value pair from the Map only after doSomething()
executes succesfully, you guarantee that doSomething()
is executed successfuly at least once for all the key/value pairs that were reomved from the Map
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With