Your code will potentially call veryCostlyOperation(name) multiple times. The problem is that there is an unsynchronized step after looking up the map:
public void request(String name) {
Resource resource = resources.get(name);
if (resource == null) {
synchronized(this) {
//...
}
}
//...
}
The get() from the map is synchronized by the map, but checking the result for null is not protected by anything. If multiple threads enter this requesting the same "name", all of them will see a null result from resources.get(), until one actually finishes costlyOperation and puts the resource into the resources map.
A simpler and working, but less scalable approach would be to go with a normal map and make the entire request method synchronized. Unless it actually turns out a problem in practice I would choose the simple approach.
For higher scalability you can fix your code by checking the map again after synchronized(this) to catch the case outlined above. It would still not give the best scalability, since the synchronized(this) only allows one thread to execute costlyOperation, whereas in many practical cases, you only want to prevent multiple executions for the same resource while allowing for concurrent requests to different resources. In that case you need some facility to synchronize on the resource being requested. A very basic example:
private static class ResourceEntry {
public Resource resource;
}
private Map<String, ResourceEntry> resources = new HashMap<String, ResourceEntry>();
public Resource request(String name) {
ResourceEntry entry;
synchronized (resources) {
entry = resources.get(name);
if (entry == null) {
// if no entry exists, allocate one and add it to map
entry = new ResourceEntry();
resources.put(name, entry);
}
}
// at this point we have a ResourceEntry, but it *may* be no loaded yet
synchronized (entry) {
Resource resource = entry.resource;
if (resource == null) {
// must create the resource
resource = costlyOperation(name);
entry.resource = resource;
}
return resource;
}
}
This is only a rough sketch. Basically, it makes a sychronized lookup for a ResourceEntry, and then synchronizes on the ResourceEntry to ensure the specific resource is only built once.