ConcurrentHashMap重新排序指令?

我正在研究ConcurrentHashMap的实现,让我感到困惑。

/* Specialized implementations of map methods */ V get(Object key, int hash) { if (count != 0) { // read-volatile HashEntry e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) { V v = e.value; if (v != null) return v; return readValueUnderLock(e); // recheck } e = e.next; } } return null; } 

  /** * Reads value field of an entry under lock. Called if value * field ever appears to be null. This is possible only if a * compiler happens to reorder a HashEntry initialization with * its table assignment, which is legal under memory model * but is not known to ever occur. */ V readValueUnderLock(HashEntry e) { lock(); try { return e.value; } finally { unlock(); } } 

和HashEntry构造函数

 /** * ConcurrentHashMap list entry. Note that this is never exported * out as a user-visible Map.Entry. * * Because the value field is volatile, not final, it is legal wrt * the Java Memory Model for an unsynchronized reader to see null * instead of initial value when read via a data race. Although a * reordering leading to this is not likely to ever actually * occur, the Segment.readValueUnderLock method is used as a * backup in case a null (pre-initialized) value is ever seen in * an unsynchronized access method. */ static final class HashEntry { final K key; final int hash; volatile V value; final HashEntry next; HashEntry(K key, int hash, HashEntry next, V value) { this.key = key; this.hash = hash; this.next = next; this.value = value; } 

放工具

 tab[index] = new HashEntry(key, hash, first, value); 

我混淆了HashEntry注释,作为JSR-133 ,一旦构造了HashEntry,所有其他线程都可以看到所有最终字段, value字段是volatile,所以我认为其他线程也可以看到??? 。 其他一点,是他说的重新排序是:HashEntry对象引用可以在完全构造之前分配给tab […](因此结果是其他线程可以看到此条目,但e.value可以为null)?

更新:我读了这篇文章,这很好。 但我需要关心这样的案例吗?

 ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); thread1: Person p=new Person("name","student"); queue.offer(new Person()); thread2: Person p = queue.poll(); 

thread2是否有可能像HashEntry一样接收未完成的构造Person对象

tab [index] = new HashEntry(key,hash,first,value); ?

对于那些对Doug Lea关于这个主题的回答感兴趣的人,他最近解释了readValueUnderLock的原因

这是对有问题的人的回应:

在ConcurrentHashMap中,get方法不需要“readValueUnderLock”,因为race remove不会使值为null。 从删除线程中,该值永远不会为空。 这意味着即使删除线程(在同一个键上)已经进行到克隆列表的前面部分,get也可能返回key的值。 只要是理想的效果,这就没问题了。

但这意味着NEW内存模型不需要“readValueUnderLock”。

但是对于OLD内存模型,由于重新排序,put可能会看到null值(罕见但可能)。

我的理解是否正确。

响应:

不完全的。 你是对的,永远不应该被召唤。 但是,JLS / JMM可以被理解为并非绝对禁止被调用,因为在构造函数中设置的finals与volatile之间所需的排序关系存在弱点(key是final,value是volatile),wrt使用条目对象读取线程。 (在JMM-ese中,韵母的排序约束超出了同步关系。)这就是doc评论(粘贴在下面)引用的问题。 没有人想到处理器/编译器可能会发现产生空值读取的任何实际漏洞,并且可能certificate不存在(也许有一天JLS / JMM修订版将填补空白以澄清这一点),但是Bill Pugh曾经建议我们无论如何只是为了保守迂腐地纠正这个问题。 回想起来,我不太确定这是一个好主意,因为它引导人们提出异国情调的理论。

这一切都可以在这里看到

我混淆了HashEntry注释,作为JSR-133,一旦构造了HashEntry,所有其他线程都可以看到所有最终字段,value字段是volatile,所以我认为其他线程也可以看到??? 。

其他线程也会看到值但是……条目的分配(进入Object [])是在初始化和锁定之后完成的。 因此,如果任何线程看到null ,它将尝试读取锁定下的值。

其他一点,是他说的重新排序是:HashEntry对象引用可以在完全构造之前分配给tab […](因此结果是其他线程可以看到此条目,但e.value可以为null)?

不,它不能b / c存在易失性赋值( value ),并且意味着所有其他操作必须事先设置(即不重新排序)。 还要记住,java对象创建是2阶段,创建一个空对象w / zero / null字段(比如使用默认的c-tor),然后调用方法(它是构造函数)。 在完成构造函数调用及其最后一次赋值之前,不能将对象赋值给任何东西(以确保正确的排序也称为之前发生的事件)

据我了解内存模型,保证写入volatile变量可以被该变量的所有后续(由同步顺序定义)读取。

但是,没有什么能保证在构造函数中写入value之后读取get()e.value (因为这些操作之间没有synchronized-with关系),因此Memory Model允许这种重新排序和显式同步在null值的情况下,必须确保我们读取正确的值。

更新:新的内存模型保证在写入volatile变量之前对非易失性变量的任何写入都可以在后续读取该volatile变量后被其他线程看到, 但反之亦然

以下是Jeremy Manson,William Pugh和Sarita Adve撰写的Java Memory Model的相关摘录:

5.1.1锁定粗化。

所有这些只是一种迂回的说法,即对正常变量的访问可以通过随后的易失性读取或锁定获取或先前的易失性写入或锁定释放来重新排序 。 这意味着正常访问可以在锁定区域内移动,但(大部分)不在其中;

因此,可以使用构造函数中的写入volatile变量对构造对象的赋值进行重新排序,以便需要检查问题。