繁体   English   中英

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

[英]ConcurrentHashMap putIfAbsent : atomicity when followed by a get() call

我想讨论一个特定的用途,我有一个并发映射来感知检查我的逻辑......

如果我使用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这样的原子性但是正确地避免了null返回? getExampleThree还有其他问题吗?

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

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

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

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

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


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

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还有其他问题吗?

只有当你认为下次调用可能不会给你一个不同的值时(如果你认为它可以在另一个线程中删除)

这些方法有不同的语义:

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

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

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

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

虽然它不会为您提供最大的并行性,但您也只有两个操作和getExampleThree ,而且正确,读取代码的其他人不太容易理解。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM