【发布时间】:2020-07-07 08:07:13
【问题描述】:
在 JDK 8 的 ConcurrentHashMap 中,tabAt 和 setTabAt 方法用于提供对 Node<K,V>[] table 中 bin 的第一个元素的易失性读/写。然而,作者认为:
请注意,对
setTabAt的调用总是发生在锁定区域内,因此原则上只需要发布顺序,而不需要完整的 volatile 语义,但目前编码为 volatile 写入是保守的。
我想知道这里的 发布顺序 是否意味着 synchronized 保证的发生前关系(监视器的解锁发生在同一监视器的每个后续锁定之前)。如果是这样,考虑到对tabAt 的调用不仅存在于synchronized 块内部,而且存在于synchronized 块之外,为什么setTabAt 被认为是保守的,而不是强制性的?例如:
/** 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...
}
}
}
}
}
另一个问题是,在上面的代码中,是否需要在 synchronized 块内调用 tabAt?在我的理解中,监视器锁已经处理了线程之间的内存可见性,例如:
- 假设 bin 中只有一个元素,比如 Node-0
- Thread-A 想要将 Node-1 插入 bin 中,就在它通过在
synchronized块外调用tabAt找到的 Node-0 之后 - 但在 Thread-A 可以锁定 Node-0 之前,Thread-B 会锁定 Node-0 并将其删除(通过调用
setTabAt) - Thread-B 释放锁后,Thread-A 获取 Node-0 的锁
- 由于线程A和线程B之间的happens-before关系由监视器锁保证,在这种情况下,在我看来没有必要调用
tabAt(这反过来又调用Unsafe.getObjectVolatile)访问并重新检查元素。
任何帮助将不胜感激。
【问题讨论】:
标签: java concurrency volatile concurrenthashmap java-memory-model