【问题标题】:ConcurrentHashMap putIfAbsent : atomicity when followed by a get() callConcurrentHashMap putIfAbsent : 后跟 get() 调用时的原子性
【发布时间】:2012-01-10 08:29:31
【问题描述】:

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

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

【问题讨论】:

  • 你允许空值作为值吗?如果是,那么 getExampleThree 中的值为 null 并不一定意味着没有与该键相关的内容。
  • 此外,如果您介意示例 1 中的竞速条件,则示例 3 中存在竞速条件(当您的方法返回值/对象时,它可能不再在映射中)。
  • 从不添加Null值,只是在getExample2中可以返回null

标签: java multithreading concurrency concurrenthashmap


【解决方案1】:

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

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

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


如果不行,可以忙循环。

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

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

【讨论】:

  • 谢谢,确实是用来做全局锁包的!
  • @JohnVint 有点超载。 ;)
  • 在看到类似问题后重新阅读这篇文章,很好的答案。非常感谢
【解决方案2】:

这些方法有不同的语义:

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

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

      我不太清楚你在找什么。让我知道这是否偏离正题,我会修改。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-26
        • 2012-05-31
        相关资源
        最近更新 更多