HashMap缓存中的同步

我有一个人们要求资源的网络应用程序。为了提高效率,使用同步哈希映射缓存此资源。这里的问题是当同时为同一个未缓存的资源发出两个不同的请求时:检索资源的操作会占用大量内存,所以我想避免为同一个资源多次调用它。 有人可以告诉我,以下代码段是否存在任何潜在问题?提前致谢。
private Map<String, Resource> resources = Collections.synchronizedMap(new HashMap<String, Resource>());

public void request(String name) {

  Resource resource = resources.get(name);

  if (resource == null) {
    synchronized(this) {
      if (resources.get(name) == null) {
        resource = veryCostlyOperation(name); // This should only be invoked once per resource...
        resources.put(resource);
      } else {
        resource = resources.get(name);
      }
    }
  }

  ...

}
    
已邀请:
一个可能的问题是您通过在
synchronized
块内执行
veryCostlyOperation()
来创建不必要的争用,因此许多线程无法同时检索其(独立)资源。这可以通过使用
Future<Resource>
作为地图的值来解决:
Map<String, Future<Resource>> map = new ConcurrentHashMap<String, Future<Resource>>();    
...
Future<Resource> r = map.get(name);
if (r == null) {
    FutureTask task = null;
    synchronized (lock) {
        r = map.get(name);
        if (r == null) {
            task = new FutureTask(new Callable<Resource>() {
                public Resource call() {
                    return veryCostlyOperation(name);
                }
            });
            r = task;
            map.put(name, r);
        }
    }
    if (task != null) task.run(); // Retrieve the resource
}

return r.get(); // Wait while other thread is retrieving the resource if necessary
    
我看到的唯一潜在问题是你同步到
this
。如果同一类中的任何其他代码也同步到
this
,则这些块中只有一个会立即运行。也许没有其他事情可以做到这一点,那很好。不过,我总是担心下一个程序员要做什么。 (或者在我忘记这段代码的三个月内我自己) 我建议创建一个通用的同步对象,然后同步它。 private final Object resourceCreationSynchObject = new Object(); 然后 synchronized(this.resourceCreationSynchObject){   ... } 否则,这完全符合您的要求。它确保不能并行调用
veryCostlyOperation
。 此外,在
synchronized
区块内第二次重新获取资源是一个很好的想法。这是必要的,并且第一次调用外部确保在资源可用时不进行同步。但是没有理由第三次称呼它。首先在
synchronized
块内,将
resource
再次设置为
resources.get(name)
,然后将该变量检查为null。这将阻止你在
else
条款中再次调用
get
。     
您的代码看起来没问题,除了您正在同步超过实际需要的代码: 使用
ConcurrentHashMap
而不是同步
HashMap
将允许多次调用get方法而不锁定。 可能不需要在
this
而不是
resources
上进行同步,但这取决于代码的其余部分。     
您的代码可能会多次调用veryCostlyOperation(name)。问题是查找地图后有一个不同步的步骤:
public void request(String name) {
    Resource resource = resources.get(name);
    if (resource == null) {
        synchronized(this) {
            //...
        }
    }
    //...
}
映射中的get()由映射同步,但检查结果为null不受任何保护。如果多个线程输入此请求相同的“名称”,则所有线程都将看到来自resources.get()的null结果,直到实际完成1,000Operation并将资源放入资源映射。 一种更简单,更有效但可扩展性更低的方法是使用法线贴图并使整个请求方法同步。除非它在实践中实际上是一个问题,否则我会选择简单的方法。 为了获得更高的可伸缩性,您可以通过在synchronized(this)之后再次检查映射来修复代码,以捕获上面概述的情况。它仍然不会提供最佳的可伸缩性,因为synchronized(this)只允许一个线程执行昂贵的操作,而在许多实际情况下,您只希望阻止同一资源的多次执行,同时允许对不同资源的并发请求。在这种情况下,您需要一些工具来同步所请求的资源。一个非常基本的例子:
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;
    }
}
这只是一个粗略的草图。基本上,它为ResourceEntry进行同步查找,然后在ResourceEntry上进行同步,以确保特定资源仅构建一次。     

要回复问题请先登录注册