想象一下,有两个线程A,B分别在地图中放置两个不同的值,分别为v1和v2,具有相同的密钥。密钥最初不在地图中 线程A调用containsKey并发现密钥不存在,但会立即挂起 线程B调用containsKey并发现密钥不存在,并且有时间插入其值v2
当线程A回来时,会发生什么? 我假设,它调用put方法反过来调用putIfAbsent 但是密钥已经由线程B插入了。所以线程A不会覆盖值
但是从这个链接我发现了 线程A恢复并插入v1,“和平”覆盖(因为put是线程安全的)线程B插入的值 Is ConcurrentHashMap totally safe?
答案 0 :(得分:5)
以下是ConcurrentHashMap将为您做的事情:
(1)键/值对不会神秘地出现在地图中。如果您尝试获取某个键的值,则可以保证获得程序中某个线程与该键一起存储的值,或者如果没有线程存储过该键的值,您将获得空引用。
(2)键/值对不会神秘地从地图上消失。如果你为之前有一个值的某个K调用get(K),并且返回null引用,那是因为程序中的某个线程存储了null。
(3)它不会使你的程序死锁或挂起或崩溃。
以下是ConcurrentHashMap将不为您做的事情:
它不会使你的程序“线程安全”。
关于线程安全最重要的事情是:完全从“线程安全”组件构建模块或程序将不使程序或模块“线程安全”。你的问题就是一个完美的例子。
ConcurrentHashMap是一个线程安全的对象。无论有多少线程同时访问它,它将保留我在上面列出的promise(1),(2)和(3)。但是,如果你的两个程序的每个线程都试图同时为同一个键的地图添加不同的值,那就是数据竞争。当其他一些线程稍后查找该键时,它获得的值将取决于哪个线程赢得了比赛。
如果程序的正确性取决于哪个线程赢得数据竞争,那么即使构建它的对象称为“线程安全”,程序也不是“线程安全的”。
答案 1 :(得分:1)
两个线程都需要使用putIfAbsent
。来自putIfAbsent(key, value)
的{{3}}(重点已添加):
这相当于
if (!map.containsKey(key)) return map.put(key, value); else return map.get(key);
,但动作是以原子方式执行的。
对put()
的调用不最终会导致对putIfAbsent()
的调用(正如您的问题似乎暗示的那样);反过来了。
尝试通过单独调用containsKey()
和put()
来达到同样的效果,需要使用自己的更高级别的同步块。
答案 2 :(得分:0)
并发哈希映射的强大实现将使用同步来通过插入新的映射条目来调用containsKey()
原子。如果在调用containsKey()
后线程A被挂起,则线程B会发现它无法获得锁定,因此无法按照您的描述调用containsKey()
。
答案 3 :(得分:0)
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会覆盖该值,当我们尝试添加已存在的密钥时。