【问题标题】:What happens when two threads trying to put the same key value in concurrent hashmap当两个线程试图将相同的键值放入 concurrenthashmap 时会发生什么
【发布时间】:2014-12-08 18:26:54
【问题描述】:

假设有两个线程 A、B 将分别在映射中放入两个不同的值 v1 和 v2,它们具有相同的键。密钥最初不存在于地图中 线程A调用containsKey,发现key不存在,立即挂起 线程B调用containsKey发现key不存在,有时间插入其值v2

当线程 A 回来时,会发生什么? 我假设,它调用 put 方法,该方法又调用 putIfAbsent 但是线程B已经插入了密钥,所以线程A不会覆盖该值

但是从这个链接我发现 线程 A 恢复并插入 v1,“和平地”覆盖(因为 put 是线程安全的)线程 B 插入的值 Is ConcurrentHashMap totally safe?

【问题讨论】:

  • answer by gd1 已经涵盖了这个。
  • 只有(指定的)individual 方法是原子的(并且“完全安全”)。如果没有更大的构造,就不可能增加原子/互斥保证的范围。我相当肯定文档说明了这一点。

标签: java multithreading


【解决方案1】:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
     HashEntry<K,V> node = tryLock() ? null :
         scanAndLockForPut(key, hash, value);
     V oldValue;
     try {
         HashEntry<K,V>[] tab = table;
         int index = (tab.length - 1) & hash;
         HashEntry<K,V> first = entryAt(tab, index);
         for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                 K k;
                 if ((k = e.key) == key ||
                     (e.hash == hash && key.equals(k))) {
                     oldValue = e.value;
                     if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                     }
                     break;
                 }
                 e = e.next;
             }
            else {
                if (node != null)
                   node.setNext(first);
               else
                    node = new HashEntry<K,V>(hash, key, value, first);
                 int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                 ++modCount;
                 count = c;
               oldValue = null;
                 break;
             }
      }
   } finally {
         unlock();
     }
     return oldValue;
 }

从 put 方法的内部实现中找到答案,当我们尝试添加已经存在的键时,put 将覆盖该值。

【讨论】:

    【解决方案2】:

    以下是 ConcurrentHashMap 将为您做的事情:

    (1) 键/值对不会神秘地出现在地图中。如果您尝试获取某个键的值,则可以保证获得程序中的某个线程使用该键存储的值,或者如果没有线程曾经为该键存储过值,您将获得空引用。

    (2) 键/值对不会神秘地从地图中消失。如果您为之前有值的某个 K 调用 get(K),然后返回一个空引用,那是因为程序中的某个线程存储了该空值。

    (3) 它不会死锁、挂起或使您的程序崩溃。

    以下是 ConcurrentHashMap 不会为您做的事情:

    它不会让你的程序“线程安全”。

    关于线程安全要记住的最重要的一点是:完全从“线程安全”组件构建模块或程序将不会使程序或模块成为“线程安全”。你的问题就是一个很好的例子,说明为什么不这样做。

    ConcurrentHashMap 是一个线程安全的对象。无论有多少线程同时访问它,它都会遵守我上面列出的承诺 (1)、(2) 和 (3)。但是,如果您的程序的两个线程都尝试同时为同一个键将不同的值放入映射中,那就是 数据竞争。当其他线程稍后查找该键时,它获得的值将取决于哪个线程赢得了比赛。

    如果您的程序的正确性取决于哪个线程赢得了数据竞争,那么您的程序就不是“线程安全的”,即使构建它的对象被称为“线程安全”。

    【讨论】:

      【解决方案3】:

      两个线程都需要使用putIfAbsent。来自the docs(强调添加)到putIfAbsent(key, value)

      这相当于

         if (!map.containsKey(key))
            return map.put(key, value);
        else
            return map.get(key);
      

      除了动作是原子执行的。

      put() 的调用最终会导致对putIfAbsent() 的调用(正如您的问题所暗示的那样);反之亦然。

      尝试通过单独调用 containsKey()put() 来达到相同的效果将需要您使用自己的更高级别的同步块。

      【讨论】:

        【解决方案4】:

        并发哈希映射的健壮实现将使用同步来通过插入新映射条目来使调用containsKey()原子。如果线程A在调用containsKey()后被挂起,线程B会发现它无法获取锁,因此无法调用containsKey()

        【讨论】:

        • Java 不是这样工作的。库无法提供例程 A() 和 B() 以便对 A() 的调用后跟对 B() 的调用将作为原子单元执行。如果调用者希望它们以原子方式执行,则由调用者提供显式同步。
        • B() 可以调用A() 并自动调用。
        • 我一定误解了 OP 的要求。我以为他/她是明确调用 containsKey(k) 然后 put(k, v)。你所说的正是 putIfAbsent(k, v) 实际所做的。
        • 我的回答本来可以更详细,但现在毫无意义,因为我认为我误解了 OP 的要求。
        猜你喜欢
        • 2016-07-23
        • 1970-01-01
        • 2019-11-15
        • 1970-01-01
        • 2014-03-18
        • 2014-08-31
        • 2022-06-18
        • 2010-12-12
        • 2019-11-03
        相关资源
        最近更新 更多