我使用过ConcurrentHashMaps,但我不确定这是否涵盖了所有基础。
我有一个Spring组件。该组件将包含一个Map。这只是外部服务中对象的快速参考。如果映射不包含匹配的String,它将调用外部服务,检索对象并将其存储在映射中。然后其他类可以使用映射来快速检索和使用。因此,在地图上只执行put()和get()操作。条目永远不会被删除。
话虽如此,我有点担心ConcurrentHashMap可能无法提供我想要的原子控制。从外部服务获取SomeObject可能很昂贵。我宁愿没有两个单独的线程几乎同时调用,导致多次调用相同的值到外部服务。
这个想法是这样的:
Map<String, SomeObject> map = Collections.concurrentHashMap(
new HashMap<String, SomeObject>());
public SomeObject getSomeObject(String key){
if (!map.containsKey(key)){
map.put(key, retrieveSomeObjectFromService(key));
}
return map.get(key);
或者这个:
Map<String, SomeObject> map = new HashMap<String, SomeObject>();
public SomeObject getSomeObject(String key){
synchronized(map){
if (!map.containsKey(key)){
map.put(key, retrieveSomeObjectFromService(key));
}
}
return map.get(key);
}
前者当然更简单,但后者将确保一个两个或多个线程不会尝试同时触发相同SomeObject的获取。或者,我想我可以尝试锁定只获取尝试检索已经处于获取过程中的SomeObject并且不阻止检索已存在的SomeObjects,但这将需要对各种字符串值的等待机制和我我不确定如何最好地实现它。
答案 0 :(得分:3)
我建议你做两件事!
快速路径,只有1个退出并发hashmap。 慢速路径,完全同步和锁定
private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
private final ReentrantLock lock = new ReentrantLock();
public Object getSomeObject(String key) {
Object value = map.get(key);
if (value == null) {
try {
lock.lock();
value = map.get(key);
if (value == null) {
value = retrieveSomeObjectFromService(key);
map.put(key, value);
}
} finally {
lock.unlock();
}
}
return value;
}
你明白为什么我们需要第二次进入锁定?离开这一点留下了一个案例,我们最终将内部对象制作了两次,并且有不同的副本漂浮在周围。
同时使用contains方法将结果赋值给null并使用nullcheck - 理解为什么更好?如果我们执行.contains然后是.get,我们只进行了2次hashmap查找。如果我只是做一个get,我可以将我的hashmap查找时间缩短一半。
另一个版本彼得建议..代码行数较少,但不是我个人的偏好:
private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
public Object getSomeObject(String key) {
Object value = map.get(key);
if (value == null) {
synchronized (map) {
value = map.get(key);
if (value == null) {
value = retrieveSomeObjectFromService(key);
map.put(key, value);
}
}
}
return value;
}