当两个线程试图在并发hashmap中放入相同的键值时会发生什么

想象一下,有两个线程A,B将分别在地图中放置两个不同的值,即v1和v2,具有相同的密钥。 密钥最初不存在于映射中线程A调用containsKey并发现密钥不存在,但是立即挂起线程B调用containsKey并发现密钥不存在,并且有时间插入其值v2

当线程A回来时,会发生什么? 我假设,它调用put方法,它反过来调用putIfAbsent但是密钥已经在线程中插入B.so线程A不会覆盖该值

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

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

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

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

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

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

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

关于线程安全最重要的事情是:完全从“线程安全”组件构建模块或程序不会使程序或模块“线程安全”。 你的问题是一个很好的例子。

ConcurrentHashMap是一个线程安全的对象。 无论有多少线程同时访问它,它将保留我在上面列出的promise(1),(2)和(3)。 但是如果你的两个程序的每个线程都尝试在同一时间为同一个键的地图放置一个不同的值,那就是数据竞争 。 当某个其他线程稍后查找该键时,它获得的值将取决于哪个线程赢得了比赛。

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

两个线程都需要使用putIfAbsent 。 从putIfAbsent(key, value)上的文档 (重点添加putIfAbsent(key, value)

这相当于

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

除了动作以primefaces方式执行。

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

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

并发哈希映射的强大实现将使用同步来通过插入新的映射条目来调用containsKey()primefaces。 如果在调用containsKey()之后线程A被挂起,则线程B会发现它无法获得锁定,因此无法按照您的描述调用containsKey()

 final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry node = tryLock() ? null : scanAndLockForPut(key, hash, value); V oldValue; try { HashEntry[] tab = table; int index = (tab.length - 1) & hash; HashEntry first = entryAt(tab, index); for (HashEntry 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(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会覆盖该值,当我们尝试添加已存在的密钥时。