4

我使用过 ConcurrentHashMaps,但我不太确定这是否会涵盖这里的所有基础。

我有一个弹簧组件。该组件将包含一个地图。这只是外部服务中对象的快速参考。如果映射不包含匹配的字符串,它将调用外部服务,检索对象并将其存储在映射中。然后其他类可以使用该映射进行快速检索和使用。因此,地图上只执行了 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 并且不会阻止检索已经存在的 SomeObject,但这需要对各种字符串值的等待机制,我'不知道如何最好地实现这一点。

4

1 回答 1

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 方法将结果分配给 value 和 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;
}
于 2013-11-04T22:08:05.247 回答