我使用过ConcurrentHashMaps,但我不太确定这是否会涵盖这里的所有基础。
我有一个弹簧组件。此组件将包含一个映射。这只是外部服务中对象的快速参考。如果映射不包含匹配的字符串,它将调用外部服务,检索对象,并将其存储在映射中。然后,其他类可以使用映射进行快速检索和使用。因此,只有 put() 和 get() 操作在映射上执行。条目永远不会被删除。
话虽如此,我有点担心ConcurrentHashMap可能无法提供我想要的原子控制。从外部服务获取某个对象可能很昂贵。我宁愿不要让两个单独的线程几乎同时调用,从而导致对外部服务的相同值进行多次调用。
这个想法是这样的:
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,但这需要对各种字符串值的等待机制,我不确定如何最好地实现这一点。
我建议你两者兼而有之!
快速路径,只需 1 个退出并发哈希图。慢速路径,完全同步和锁定
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;
}
你明白为什么我们需要第二个进入锁内吗? 省略这一点会留下一种情况,即我们最终制作内部对象两次,并使其的不同副本漂浮。
还使用包含方法将结果分配给值和 nullcheck 与 - 了解为什么这样更好? 如果我们做一个.contains,然后做一个.get,我们只做了2个哈希图查找。 如果我只是做一个get,我可以将我的哈希图查找时间减少一半。
彼得建议的另一个版本......更少的代码行,但不是我个人的偏好:
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;
}