I maintain a map of high scores for different games. When I receive a new score, I want to be able to check if the score is higher than the current high score, and if so, make it the new high score for that game in the map.
ConcurrentHashMap looks like the way to go, but I'm surprised to see that there doesn't seem to be a simple way to atomically check the current value in the map and update it conditionally.
I have tried using compute, but it seems to me there is no way to say "As a result of my computation I'd like to leave the current entry in place." I was hoping that returning null from the compute function would achieve this, but it removes the current entry.
My example code:
import java.util.concurrent.ConcurrentHashMap;
public class ConcHashMapTest{
public static void main(String[] args){
ConcurrentHashMap<String,String> h = new ConcurrentHashMap<>();
h.put("TEST","value1");
/*
The question is, can I do an atomic CHECK and update ONLY if
I want to?
I know I can use compute, but do you HAVE to change the map
or can you return null and leave the mapping as it is?
*/
h.compute("TEST",(k,v) -> {
if (v.equals("changeme")){
return "CHANGED";
}
else {
return null;
}
});
System.out.println("Now hashmap is "+h);
}
}
This returns:
Now hashmap is {}
I could of course just return the same value as I found there, but presumably that would cause a lot of updating of that value? Perhaps that's the way to go, though? I don't think so, though, because one of the points of the concurrenthashmap is to allow overlapping reads for speed, and syncronize updates, and to do this it creates a happens before only if there is an update. So I don't want to be updating every time I check the value surely?
Thank you!
EDIT: Oh wait, I think I'm getting confused. The benefit to the concurrent hashmap is that when I'm only READING the high scores (which I do a lot), I can have overlapping reads. And if I want atomicity of updates, my calls HAVE to be syncronised somewhere so that they're sequential?
So I think that using compute and updating the value with itself if I want to leave it unchanged must be the right thing to do? I believe the syncronising only locks that that one mapping (or at least bucket), not the whole map..
EDIT2: Thinking further about it, I'm tempted not to try for atomicity. Although I will be getting potentially thousands of possible highscores for a game every second, genuine high scores will be pretty rare. The chance of two high scores arriving at exactly the same moment such that there is an interleaving problem with:
if (score > map.get("SCORE")){
map.put("SCORE",score);
}
is so close to zero as to make no difference. I think I'll just leave a small (almost theoretical) hole where I could fail to record a high score.
The ConcurrentHashMap class is optimized for threaded operations, so the provided methods should all function in an atomic manner.
Utilize the computeIfPresent method.
Here is an example.
Here is the OpenJDK source for the computeIfPresent method.
At least for this method, the synchronization is on the f Node object, rather than the method, or the instance.
The entire class is quite optimized.