gpt4 book ai didi

java - ConcurrentHashMap 中的可变语义

转载 作者:行者123 更新时间:2023-12-01 14:26:26 25 4
gpt4 key购买 nike

ConcurrentHashMap JDK 8 的方法 tabAtsetTabAt用于提供 Node<K,V>[] table 中 bin 的第一个元素的 volatile 读/写.然而,作者评论说:

Note that calls to setTabAt always occur within locked regions, and so in principle require only release ordering, not full volatile semantics, but are currently coded as volatile writes to be conservative.

我想知道这里的发布顺序是否意味着由synchronized保证的先行关系(监视器的解锁发生在同一监视器的每个后续锁定之前) .如果是这样,为什么 setTabAt被认为是保守的,但不是强制性的,因为对 tabAt 的调用不仅存在于内部,也存在于外部synchronized block ?例如:

    /** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//...
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// -> tabAt called here, outside the synchronized block
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
// -> tabAt called here, inside the synchronized block
if (tabAt(tab, i) == f) {
// do insertion...
}
}
}
}
}

另一个问题是,在上面的代码中,是对 tabAt 的调用在synchronized里面有必要阻止吗?在我的理解中,监视器锁已经处理了线程之间的内存可见性,例如:

  1. 假设 bin 中只有一个元素,比如 Node-0
  2. Thread-A 想在 bin 中插入 Node-1,就在它通过调用 tabAt 找到的 Node-0 之后外面synchronized阻止
  3. 但在 Thread-A 可以锁定 Node-0 之前,Thread-B 锁定 Node-0 并将其删除(通过调用 setTabAt )
  4. Thread-A在Thread-B释放锁后获取Node-0的锁
  5. 由于线程 A 和线程 B 之间的 happens-before 关系由监视器锁保证,在这种情况下,在我看来,没有必要调用 tabAt。 (依次调用 Unsafe.getObjectVolatile )以访问并重新检查该元素。

如有任何帮助,我们将不胜感激。

最佳答案

在 java-8 中,您提到的那个方法定义为:

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}

例如在jdk-13中,已经是release了:

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putReferenceRelease(tab, ((long)i << ASHIFT) + ABASE, v);
}

而且,据我所知,应该与 一起工作:

static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getReferenceAcquire(tab, ((long)i << ASHIFT) + ABASE);
}

因此您可以将 setTabAt 视为 set release 并将 tabAt 视为 get acquire

release 这里的意思是release/acquire semantics ,我在这个 answer 中谈到过.关键是 volatile 写入在某些情况下(顺序一致性)做的“太多”,就像这里的那个。

(jdk-13 的)源代码中有评论说(关于 putReferenceRelease 包括)这是一个“弱(呃)volatile”:

Versions of putReferenceVolatile... that do not guarantee immediate visibility of the store to other threads...

synchronized 部分仅在 线程也使用相同的锁时提供内存可见性保证;否则所有的赌注都没有了。看来这是您缺少的部分。 Here is a more descriptive answer这解释了 synchronized 部分是如何被严重破坏的。

关于java - ConcurrentHashMap 中的可变语义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62770750/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com