Reputation: 4936
I'm maintaining multi-threaded legacy code that uses ConcurrentHashMap
.
There are operations of add and remove in other methods.
In the following code, at some point after collecting few values from the map, it throws NullPointerException
when executing synchronize(value)
.
public class MyClass{
private final Map<MyObj, Map<String, List<String>>> conMap = new ConcurrentHashMap<>();
//...
public void doSomthing((MyObj id){
List<Map<String, List<String>>> mapsList = new LinkedList<>();
for(MyObj objId: conMap.keySet()){
if(objId.key1.equals(id.key1)){
mapsList.add(conMap.get(objId));
}
}
for(Map<String, List<String>> map: mapsList){
synchronized(map){ // <-- NullPointerException here
//...
}
}
//...
}
I have a feeling that maybe during the iteration in the first loop, records are being remove. And when the line:
mapsList.add(conMap.get(objId));
being executed, objId
no longer exist and mapsList
adding null and as a result, during the second loop NullPoinerException
is thrown.
Is there any other reason to get this exception?
Upvotes: 0
Views: 238
Reputation: 298389
You have fallen for the Check-Then-Act anti-pattern. It implies checking a condition (like the presence of a key), followed by acting upon it (like calling get
), ignoring the possibility that the condition may have changed in-between.
So you encounter a particular key when iterating over conMap.keySet()
, but by the time you’re invoking conMap.get(objId)
, the key might not be in the map anymore, which is reported by returning null
.
It’s strongly recommended to use a key type having a suitable hashCode
/equals
implementation, so you don’t need to iterate over the entire map to find matches but can use a single get(id)
.
However, when you have to iterate over the map and need the values, iterate over the entry set instead of the key set.
public void doSomething(MyObj id){
// see https://stackoverflow.com/q/322715/2711488
List<Map<String, List<String>>> mapsList = new ArrayList<>();
for(Map.Entry<MyObj, Map<String, List<String>>> e: conMap.entrySet()){
if(e.getKey().key1.equals(id.key1)){
mapsList.add(e.getValue());
}
}
for(Map<String, List<String>> map: mapsList){
synchronized(map) {
//...
}
}
}
Upvotes: 1