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);
}
}
}
...
}
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 requests come for the same uncached resource at the same time: the operation retrieving the resources takes up a lot of memory, so I want to avoid calling it more than once for the same resource.
Can somebody please tell me if there is any potential problem with the following snippet? Thanks in advance.
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);
}
}
}
...
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
一个可能的问题是,通过在
synchronized
块内执行veryCostlyOperation()
会产生不必要的争用,从而导致许多线程无法同时检索其(独立)资源。这可以通过使用Future
作为地图的值来解决:One possible problem is that you create unnecessary contention by executing
veryCostlyOperation()
inside asynchronized
block, so that many threads cannot retrieve their (independent) resources at the same time. This can be solved by usingFuture<Resource>
as values of the map:我发现的唯一潜在问题是您同步到
this
。如果同一类中的任何其他代码也同步到this
,则只会同时运行这些块之一。也许没有其他东西可以做到这一点,那很好。不过,我总是担心下一个程序员会做什么。 (或者我自己在三个月内忘记了这段代码)我建议创建一个通用同步对象,然后同步到它。
否则
,这正是您所要求的。它确保
veryCostlyOperation
不能被并行调用。此外,在
synchronized
块中再次重新获取资源也是一个很好的想法。这是必要的,并且外部的第一次调用可确保您在资源已经可用时不会同步。但没有理由第三次调用它。在synchronized
块中,首先将resource
再次设置为resources.get(name)
,然后检查该变量是否为 null。这将防止您必须在else
子句中再次调用get
。The only potential problem I see is that you synchronize to
this
. If any other code in the same class also synchronizes tothis
, only one of those blocks will run at once. Maybe there's nothing else that does this, and that's fine. I always worry about what the next programmer is going to do, though. (or myself in three months when I've forgotten about this code)I would recommend creating a generic synch object and then synch'ing to that.
then
Otherwise, this does exactly what you're asking for. It ensures that
veryCostlyOperation
cannot be called in parallel.Also, it's great thinking to re-get the resource a second time within the
synchronized
block. This is necessary, and the first call outside makes sure that you don't synchronize when the resource is already available. But there's no reason to call it a third time. First thing inside thesynchronized
block, setresource
again toresources.get(name)
and then check that variable for null. That will prevent you from having to callget
again inside theelse
clause.您的代码看起来不错,只是您同步的内容超出了实际需要的内容:
使用
ConcurrentHashMap
而不是同步的HashMap
将允许多次调用 get 方法而无需锁定.在
this
上同步而不是在resources
上同步可能没有必要,但这取决于代码的其余部分。Your code looks ok, except that you are synchronizing more than actually required:
Using a
ConcurrentHashMap
instead of a synchronizedHashMap
would allow multiple invocations of the get method without locking.Synchronizing on
this
instead ofresources
is probably not necessary, but it depends on the rest of your code.您的代码可能会多次调用veryCostlyOperation(name)。问题在于,查找映射后存在一个不同步的步骤:
映射中的 get() 已由映射同步,但检查结果是否为 null 不受任何保护。如果多个线程进入此请求相同的“名称”,则所有线程都会从 resources.get() 看到 null 结果,直到一个线程实际完成 costlyOperation 并将资源放入资源映射中。
一种更简单、有效但可扩展性较差的方法是使用法线贴图并使整个请求方法同步。除非在实践中确实出现问题,否则我会选择简单的方法。
为了获得更高的可扩展性,您可以通过在synchronized(this)之后再次检查地图来修复您的代码以捕获上述情况。它仍然无法提供最佳的可扩展性,因为synchronized(this)只允许一个线程执行costlyOperation,而在许多实际情况下,您只想防止同一资源的多次执行,同时允许对不同资源的并发请求。在这种情况下,您需要一些工具来同步所请求的资源。一个非常基本的例子:
这只是一个粗略的草图。基本上,它对 ResourceEntry 进行同步查找,然后在 ResourceEntry 上进行同步,以确保特定资源仅构建一次。
Your code will potentially call veryCostlyOperation(name) multiple times. The problem is that there is an unsynchronized step after looking up the map:
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:
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.