Java HashMaps只包含put和get—可能存在并发问题?

Java HashMaps只包含put和get—可能存在并发问题?,java,multithreading,concurrency,hashmap,synchronized,Java,Multithreading,Concurrency,Hashmap,Synchronized,我使用过ConcurrentHashMaps,但我不确定它是否涵盖了这里的所有基础 我有一个弹簧组件。此组件将包含一个映射。这只是外部服务中对象的快速参考。如果映射不包含匹配字符串,它将调用外部服务,检索对象,并将其存储在映射中。然后其他类可以使用映射进行快速检索和使用。因此,在映射上只执行put()和get()操作。条目永远不会被删除 尽管如此,我还是有点担心ConcurrentHashMap可能无法提供我想要的原子控制。从外部服务获取SomeObject可能会很昂贵。我不希望让两个独立的线程

我使用过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 Map=Collections.concurrentHashMap(
新HashMap());
公共SomeObject getSomeObject(字符串键){
如果(!map.containsKey(键)){
map.put(key,retrieveSomeObjectFromService(key));
}
返回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);
}
Map Map=newhashmap();
公共SomeObject getSomeObject(字符串键){
同步(地图){
如果(!map.containsKey(键)){
map.put(key,retrieveSomeObjectFromService(key));
}
}
返回map.get(key);
}

前者当然更简单,但后者可以确保一个或两个以上的线程不会同时触发对同一个SomeObject的抓取。或者,我想我可以尝试锁定仅获取已在抓取过程中且不会阻止检索SomeObject的尝试它已经存在,但这需要对各种字符串值使用等待机制,我不确定如何最好地实现它。

我建议您同时使用这两种方法

快速路径,只需从并发hashmap中取出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;
}
private final ConcurrentHashMap=new ConcurrentHashMap();
private final ReentrantLock lock=new ReentrantLock();
公共对象getSomeObject(字符串键){
对象值=map.get(键);
如果(值==null){
试一试{
lock.lock();
value=map.get(键);
如果(值==null){
值=从服务(键)检索omeobjects;
map.put(键、值);
}
}最后{
lock.unlock();
}
}
返回值;
}
你明白为什么我们需要第二个进入锁的内部吗?如果不这样做,我们最终会制作两次内部对象,并有不同的副本四处浮动

另外,使用contains方法将结果赋值给value和nullcheck-vs-明白为什么这样更好吗?如果我们先执行.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;
}
private final ConcurrentHashMap=new ConcurrentHashMap();
公共对象getSomeObject(字符串键){
对象值=map.get(键);
如果(值==null){
同步(地图){
value=map.get(键);
如果(值==null){
值=从服务(键)检索omeobjects;
map.put(键、值);
}
}
}
返回值;
}

+1虽然我看不出锁提供了哪些同步的功能,但代码会更简单。@PeterLawrey-你的朋友博客:)“与使用同步的语言监视器相比,ReentrantLock提供了最佳的未竞争性能,并且随着竞争的增加,可扩展性会显著提高。”很好的链接,尽管我不同意Java 6和7的低争用锁性能。Java 5也是如此,但同步从那时起有所改进。在任何情况下,你都可以说同步更简单。如果我想要简单的代码,我会使用Scala和匿名函数withLock来读取,就像同步一样,但实际上在t下使用锁he hood;)你是对的,测试是JDK6,对JDK7或JDK8性能没有任何保证。我倾向于缓慢地改变这种习惯,因为竞争时同步成本大约为300纳秒(更快的非竞争),从服务中检索SomeObjectsFromService的
成本必须在这个水平左右,才能发现任何差异。如果成本更高,则可能是速度的一半,但您永远不会知道。