ConcurrentHashMap putIfAbsent:当跟随get()调用时的原子性

时间:2012-01-10 08:29:31

标签: java multithreading concurrency concurrenthashmap

我想讨论我对并发映射的特定用法,以检查我的逻辑......

如果我使用ConcurrentHashMap,我可以做熟悉的

private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<K, V>();

public V getExampleOne(K key) {
    map.putIfAbsent(key, new Object());
    return map.get(key);
}

但我意识到存在竞争条件,如果我从putIfAbsentget之间的地图中删除该项,则上述方法会返回集合中存在的时间更长。这可能会或可能不会很好,但我们假设对于我的用例,它不行。

我真正喜欢的是让整个事物成为原子。所以,

public V getExampleTwo(K key) {
    return map.putIfAbsent(key, new Object());
}

但随着这扩展到

if (!map.containsKey(key))
   return map.put(key, value);     [1]
return map.get(key);

对于第[1]行,第一次使用会返回null(即map.put将返回上一个值,第一次使用时为null)。

我不能让它在此实例中返回null

这让我有类似的东西;

public V getExampleThree(K key) {
    Object object = new Object();
    V value = locks.putIfAbsent(key, object);
    if (value == null)
        return object;
    return value;
}

所以,最后,我的问题;上面的例子在语义上有何不同? getExampleThree确保getExampleTwo之类的原子性,但是正确地避免空值返回吗? getExampleThree还有其他问题吗?

我希望对这些选择进行一些讨论。我意识到我可以使用非ConcurrentHashMap并在调用我的get方法的客户端和从地图中删除的方法同步,但这似乎打败了ConcurrentHashMap的目的(非阻塞性质)。 这是我保持数据准确的唯一选择吗?

我想这是你选择ConcurrentHashMap的原因之一;它在您与之交互时的可见/最新/显示,但如果旧数据将成为一个问题,可能会产生进一步的影响......

4 个答案:

答案 0 :(得分:5)

听起来您正在尝试为密钥创建全局锁定对象。

我不会删除一个可能几乎立即重新创建的条目,而是在您确定不再需要它时,我只会删除该条目。

否则,如果您将其用于锁定,则可以在同一个键的不同对象上使用两个线程锁定。


如果不行,你可以忙着循环它。

public V getExampleOne(K key) {
    for(Object o = null, ret = null; (ret = map.get(key)) == null; )
        map.putIfAbsent(key, o == null ? o = new Object() : o);
    return ret;
}

一旦循环存在,它仍然可以被删除或替换,因此它实际上与。

基本相同
public V getExampleThree(K key) {
    Object o = new Object();
    map.putIfAbsent(key, o);
    Object ret = map.get(key);
    return ret == null ? o : ret;
}
  

所以,最后,我的问题;上面的例子在语义上有何不同?。

差异只是显而易见的。

  

getExampleThree是否确保像getExampleTwo这样的原子性但是正确地避免了null返回?

  

getExampleThree还有其他问题吗?

只有当您认为下次通话可能不会给您一个不同的价值时(如果您认为可以在另一个主题中将其删除)

答案 1 :(得分:3)

这些方法有不同的语义:

  • getExampleOne不是原子的。
  • 如果将新对象插入到地图中,则getExampleTwo返回null。这与getExampleOne的行为不同,但它是原子的。
  • getExampleThree可能就是你想要的。它是原子的,它会在putIfAbsent调用的时间点之后返回地图中的对象。但是当nulls是您的应用程序中的有效值时,这是一个问题。那么空返回值是不明确的。

但是,根据情况,当您使用返回值时,它可能不是实际对象。然后,您需要显式锁定。

答案 2 :(得分:0)

为什么不简单地使用第一个版本并同步方法?

public synchronized V getExampleOne(K key) {
    map.putIfAbsent(key, new Object());
    return map.get(key);
}

虽然它不能为您提供最大的并行性,但您也只有两个操作getExampleThree,而且正确,对于阅读代码的其他人来说,可读性较差且不易理解。

答案 3 :(得分:0)

我认为你会发现诀窍是假设你将是非原子的并处理它。

我不太清楚你在寻找什么。让我知道如果这是切断的,我会修改。

也许您正在寻找类似的东西:

private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap();

/*
 * Guaranteed to return the object replaced.
 * 
 * Note that by the time this method returns another thread 
 * may have already replaced the object again.
 * 
 * All we can guarantee here is that the return value WAS 
 * associated with the key in the map at the time the entry was 
 * replaced.
 * 
 * A null return implies that either a null object was associated
 * with the specified key or there was no entry in the map for 
 * the specified key wih 'null' as it's 'value'.
 */
public Object consistentReplace ( String key, Object newValue ) {
  Object oldValue = map.get(key);
  while ( !map.replace(key, oldValue, newValue) ) {
    // Failed to replace because someone got in there before me.
    oldValue = map.get(key);
  }
  return oldValue;
}