5

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

如果我用过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);
}

但我意识到存在竞争条件putIfAbsent,如果我从和之间的映射中删除项目get,上面的方法将返回集合中不再存在的东西。这可能会也可能不会,但是让我们假设对于我的用例来说,这是不行的。

我真正想要的是让整个事情都是原子的。所以,

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

which for line [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 的部分原因;在您与之交互时它是可见的/最新的/准确的,但是如果旧数据将成为问题,则可能会进一步产生影响......

4

4 回答 4

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

仅当您相信下一个调用可能不会给您不同的值时(如果您相信它可以在另一个线程中删除)

于 2012-01-10T08:52:29.203 回答
3

这些方法具有不同的语义:

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

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

于 2012-01-10T08:45:27.053 回答
0

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

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

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

于 2012-01-10T10:48:57.803 回答
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;
}
于 2012-01-12T19:48:32.027 回答