ConcurrentHashMap.get() 如何防止脏读?



我正在查看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)一般不会阻塞,因此可能与更新操作(包括putremove)重叠。检索反映最近完成的更新操作的结果,这些操作在开始时保持不变。(更正式地说,给定键的更新操作与报告更新值的该键的任何(非空)检索具有发生关系。

这通常不是问题,因为如果get方法获取了锁,则get永远不会返回不会发生的结果,从而阻止另一个线程中的更新操作。您只是得到结果,就好像get调用发生在更新操作开始之前一样。

因此,如果您不介意get发生在更新之前还是之后,那么您也不应该介意它在更新期间发生,因为期间之前之间没有明显的差异。如果您确实希望get在更新发生,则需要从更新线程发出更新已完成的信号;无论如何,等待获取锁都不会实现这一点,因为您可能会在更新发生之前获得锁(在这种情况下,您将获得与未获取锁相同的结果)。

最新更新