繁体   English   中英

当两个线程试图将相同的键值放入并发哈希表时会发生什么

[英]What happens when two threads trying to put the same key value in concurrent hashmap

想象有两个线程A,B将在映射中分别放置两个不同的值v1和v2,它们具有相同的键。 该键最初在映射中不存在。线程A调用containsKey并发现该键不存在,但立即挂起。线程B调用containsKey并发现该键不存在,并有时间插入其值v2

当线程A返回时,会发生什么? 我假设,它调用了put方法,该方法又调用了putIfAbsent但密钥已经被线程B插入。因此线程A将不会覆盖该值

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

这是ConcurrentHashMap将为您做的事情:

(1)键/值对不会神秘地出现在地图中。 如果尝试获取某个键的值,则可以确保获取程序中某个线程与该键一起存储的值,如果没有线程为该键存储值,则将获得null引用。

(2)键/值对不会神秘地从地图上消失。 如果您为以前具有值的K调用get(K),并且返回空引用,那是因为程序中的某些线程存储了空值。

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

这是ConcurrentHashMap 不能为您提供的功能:

它不会使您的程序“线程安全”。

关于线程安全性要记住的最重要的事情是:完全从“线程安全”组件构建模块或程序不会使程序或模块“线程安全”。 您的问题是为什么不这样做的一个很好的例子。

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

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

两个线程都需要使用putIfAbsent docs (添加重点)到putIfAbsent(key, value)

这相当于

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

除了动作是原子执行的。

调用put()最终不会导致调用putIfAbsent() (正如您的问题似乎暗示的那样); 相反。

尝试通过分别调用containsKey()put()来实现相同的效果,将需要您使用自己的更高级别的同步块。

并发哈希映射的可靠实现将使用同步,以在插入新映射条目的情况下使调用containsKey()原子。 如果线程A在调用containsKey()之后挂起,则线程B会发现它无法获取该锁,因此无法按照您所描述的那样调用containsKey()

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将覆盖该值。

暂无
暂无

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

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