【问题标题】:Java HashMaps with only put and get - possibly concurrency issues?只有 put 和 get 的 Java HashMaps - 可能是并发问题?
【发布时间】:2013-11-15 16:20:12
【问题描述】:

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

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

【问题讨论】:

    标签: java multithreading concurrency hashmap synchronized


    【解决方案1】:

    我建议你两者都做一点!

    快速路径,只需 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;
    }
    

    你明白为什么我们需要第二次进入锁吗?如果不考虑这一点,我们最终会制作两次内部对象,并让不同的副本漂浮在周围。

    同时将结果分配给 value 和 nullcheck vs 使用 contains 方法 - 明白为什么这样更好吗?如果我们做一个 .contains 然后一个 .get,我们只是做了 2 个 hashmap 查找。如果我只是做一个 get,我可以将我的 hashmap 查找时间减少一半。

    Peter 建议的另一个版本.. 代码行数更少,但不是我个人的偏好:

    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;
    }
    

    【讨论】:

    • +1 虽然我没有看到什么锁给了你同步没有,但代码会更简单。
    • @PeterLawrey - 你的朋友博客 :) mechanical-sympathy.blogspot.com/2011/11/… "与使用同步的语言监视器相比,ReentrantLock 提供了最佳的非竞争性能,并且随着争用的增加,可扩展性显着提高。"
    • 很好的链接,尽管我不同意 Java 6 和 7 的低竞争锁性能。Java 5 确实如此,但从那时起同步得到了改进。无论如何,您都可以说同步更简单。
    • 好吧,如果我想要简单的代码,我会使用带有匿名函数 withLock 的 Scala 来像同步一样读取,但实际上在后台使用锁;) 你是对的,测试是 JDK6,不对 JDK7 或 JDK8 性能的保证。不过,我改变这些习惯的速度往往很慢
    • 竞争时的同步成本约为 300 ns(非竞争更快),retrieveSomeObjectFromService 的成本必须在此水平附近才能注意到任何差异。如果它更多,它可能是一半的速度,但你永远不会知道。
    猜你喜欢
    • 2010-10-20
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-20
    • 2012-02-09
    • 1970-01-01
    相关资源
    最近更新 更多