我正在查看ConcurrentHashMap
的源代码,想知道get()
方法如何在没有任何监视器的情况下工作,这是代码:
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))) // mark here for possible dirty read
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek)))) // mark here for possible dirty read
return e.val;
}
}
return null;
}
我标记的两条线在做同样的事情:检查当前Node<K, V>
的key
是否等于所需的key
。如果true
,将返回其对应的值。但是,如果另一个线程在return
之前切入并从数据结构中remove()
此节点,该怎么办?由于局部变量e
仍然保存已删除节点的引用,因此 GC 将保留它,并且get()
方法仍将返回已删除的值,从而导致脏读。
我错过了什么吗?
它不会:
检索操作(包括
get
)一般不会阻塞,因此可能与更新操作(包括put
和remove
)重叠。检索反映最近完成的更新操作的结果,这些操作在开始时保持不变。(更正式地说,给定键的更新操作与报告更新值的该键的任何(非空)检索具有发生前关系。
这通常不是问题,因为如果get
方法获取了锁,则get
永远不会返回不会发生的结果,从而阻止另一个线程中的更新操作。您只是得到结果,就好像get
调用发生在更新操作开始之前一样。
因此,如果您不介意get
发生在更新之前还是之后,那么您也不应该介意它在更新期间发生,因为期间和之前之间没有明显的差异。如果您确实希望get
在更新后发生,则需要从更新线程发出更新已完成的信号;无论如何,等待获取锁都不会实现这一点,因为您可能会在更新发生之前获得锁(在这种情况下,您将获得与未获取锁相同的结果)。