我想知道当我们尝试在调整大小时读取ConCurrentHashMap时可能发生的情况。
我知道在读取过程中,第一次尝试总是不同步的。在第二次尝试中,它将尝试获取锁并重试。
但是如果它发生在调整大小期间,它将如何工作?
谢啦
通过查看来源:
并发HashMap包含一个或多个段,具体取决于并发级别。
在重新散列期间,一个段被锁定,新表在旧表旁边构建,然后在最后替换它。
如果在段的重新散列期间调用get()
并且您的键存储在该段中,您将访问该段的旧表而不带锁,如果找到并且值不为空,则将返回。如果值为空,则调用将阻塞,直到重新散列完成,并且该值将在锁下再次读取。
Java8/11,
让我们先看看get()
方法,
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek))) // found and just return
return e.val;
}
else if (eh < 0) // -1 for forwarding node or red tree
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) { // iterate linked list
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
Java8用自旋锁(CAS和循环)代替段锁,并在碰撞桶的第一个节点上同步,以获得更好的并发性能。
有两个表用于调整大小,并通过易失性
声明强制可见。
/**
* The array of bins. Lazily initialized upon first insertion.
* Size is always a power of two. Accessed directly by iterators.
*/
transient volatile Node<K,V>[] table;
/**
* The next table to use; non-null only while resizing.
*/
private transient volatile Node<K,V>[] nextTable;
调整大小基本上是将数据从table
复制到nextTable
。完成后,nextTable将设置为table
,旧的table
将作为垃圾免费。
现在通过get
方法查找目标桶时,如果它还没有被处理,我们只是通过比较键来查找值。如果它已经被移动到新表中,则会在这个桶中设置一个转发节点,指向新表,因此e. find(h,key)
会通过键在nextTable
中搜索bucket。