【问题标题】:Synchronization on ConcurrentHashMapConcurrentHashMap 上的同步
【发布时间】:2014-04-24 09:10:40
【问题描述】:

在我的应用程序中,我使用的是 ConcurrentHashMap,我需要这种类型的“自定义 put-if-absent”方法以原子方式执行。

public boolean putIfSameMappingNotExistingAlready(String key, String newValue) {
    String value;
    synchronized (concurrentHashMap) {
    if (value = concurrentHashMap.putIfAbsent(key, newValue)) == null) {
          // There was no mapping for the key
      return true;
      } else { if (value.equals(newValue)) {
                // The mapping <key, newValue> already exists in the map
                return false;
            } else {
                concurrentHashMap.put(key, newValue);
                return true;
            }
        }
     }
    } 

我读到(在并发包文档中)

并发集合是线程安全的,但不受单个排除锁的控制。

所以你不能在 ConcurrentHashMap 上获得排他锁。

我的问题是:

  1. 上面的代码是线程安全的吗?对我来说似乎可以保证同步块中的代码只能由单个线程同时执行,但我想确认一下。

  2. 在这种情况下使用 Collections.synchronizedMap() 代替 ConcurrentHashMap 不是更“干净”吗?

非常感谢!

【问题讨论】:

  • 真的是'concurrentHashMap.putIfAbsent(msg, msg)'吗?
  • @isnot2bad 不,我更正了,谢谢
  • 你能用比较和设置循环代替锁吗? blog.slaks.net/2013-07-22/thread-safe-data-structures
  • @ovdsrn 您在 EDIT 中建议的代码将不起作用,因为 putIfAbsent 如果已经有此键的映射,则不会执行任何操作。
  • @isnot2bad 你说的很对,傻我,我会删除编辑,因为它没有意义。

标签: java multithreading concurrency synchronized java.util.concurrent


【解决方案1】:

以下代码使用比较和设置循环(如 SlakS 建议的那样)来实现线程安全(注意无限循环):

/**
 * Updates or adds the mapping for the given key.
 * Returns true, if the operation was successful and false,
 * if key is already mapped to newValue.
 */
public boolean updateOrAddMapping(String key, String newValue) {
    while (true) {
        // try to insert if absent
        String oldValue = concurrentHashMap.putIfAbsent(key, newValue);
        if (oldValue == null) return true;

        // test, if the values are equal
        if (oldValue.equals(newValue)) return false;

        // not equal, so we have to replace the mapping.
        // try to replace oldValue by newValue
        if (concurrentHashMap.replace(key, oldValue, newValue)) return true;

        // someone changed the mapping in the meantime!
        // loop and try again from start.
    }
}

【讨论】:

  • 您的建议不会知道映射 是否已经在映射中。您将能够确定是否存在密钥映射。
  • 注意,如果Java 7,nullSafeEquals()可以替换为Objects.equals()
  • @fge 很好。不确定是 Java 7 还是 8。
  • +1 我要说的唯一评论是concurrentHashMap.putIfAbsent 每次都会获得一个锁,如果有很多replaceObject.equals 调用正在进行,这可能会降低吞吐量。我会先得到oldValue,然后如果它为空,则进行 putItAbsent 测试(同时重新分配 oldValue)。
  • @JohnVint 我通过使用putIfAbsent 的返回值完全删除了get。 (如果条目已经存在,putIfAbsent 在吞吐量方面应该像get 一样工作)。现在代码更简单了。
【解决方案2】:

通过像这样在整个集合上同步,您实际上是在用您自己的钝力方法替换并发集合中的细粒度同步。

如果您没有在其他地方使用并发保护,那么您可以为此使用标准的HashMap 并将其包装在您自己的同步中。使用synchronizedMap 可能有效,但不会涵盖多步骤操作,例如上面放置、检查、放置的位置。

【讨论】:

    猜你喜欢
    • 2010-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-14
    相关资源
    最近更新 更多