I have a piece of code that can be executed by multiple threads that needs to perform an I/O-bound operation in order to initialize a shared resource that is stored in a ConcurrentMap
. I need to make this code thread safe and avoid unnecessary calls to initialize the shared resource. Here's the buggy code:
private ConcurrentMap<String, Resource> map;
// .....
String key = "somekey";
Resource resource;
if (map.containsKey(key)) {
resource = map.get(key);
} else {
resource = getResource(key); // I/O-bound, expensive operation
map.put(key, resource);
}
With the above code, multiple threads may check the ConcurrentMap
and see that the resource isn't there, and all attempt to call getResource()
which is expensive. In order to ensure only a single initialization of the shared resource and to make the code efficient once the resource has been initialized, I want to do something like this:
String key = "somekey";
Resource resource;
if (!map.containsKey(key)) {
synchronized (map) {
if (!map.containsKey(key)) {
resource = getResource(key);
map.put(key, resource);
}
}
}
Is this a safe version of double checked locking? It seems to me that since the checks are called on ConcurrentMap
, it behaves like a shared resource that is declared to be volatile
and thus prevents any of the "partial initialization" problems that may happen.
yes it' safe.
If map.containsKey(key)
is true, according to doc, map.put(key, resource)
happens before it. Therefore getResource(key)
happens before resource = map.get(key)
, everything is safe and sound.