Synchronization in a HashMap cache

前端 未结 4 631
孤独总比滥情好
孤独总比滥情好 2021-01-18 03:03

I\'ve got a web application where people ask for resources. This resources are cached using a synchronized hash map for efficiency. The problem here is when two different re

4条回答
  •  长发绾君心
    2021-01-18 04:00

    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 resources = new HashMap();
    
    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.

提交回复
热议问题