在数据竞争期间,线程是否可以读取易失性变量的初始空值?特别是当在构造函数中为其分配非空值时?
- 让我困惑的是这个。
ConcurrentHashMap 中 HashEntry 的 Java 文档 (jdk1.6.0_16)
...由于值字段是易失性的,而不是最终的,因此在通过数据竞争读取时,非同步读取器看到 null 而不是初始值是合法的 Java 内存模型。尽管导致这种情况的重新排序不太可能实际发生,但 Segment.readValueUnderLock 方法可用作备份,以防在非同步访问方法中看到 null(预初始化)值。
这里是ConcurrentHashMap#Segment的get方法的实现
<前><代码> V get(对象键, int 哈希值) { if (count != 0) { // 读易失性 HashEntry e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) { V v = e.值; if (v != null) 返回v; 返回 readValueUnderLock(e); // 重新检查 } e = e.下一个; } } 返回空值; }和 readValueUnderLock
V readValueUnderLock(HashEntry e) {
lock();
try {
return e.value;
} finally {
unlock();
}
}
根据我的阅读和理解,每个线程都会读取最新值的易失性变量。
那么线程什么时候会读取初始空值呢?特别是在 HashEntry 中,其值是在构造函数完成之前分配的。 (另请注意,HashEntry 的引用永远不会逃逸其构造函数。)
我很困惑,有人可以解释一下 ConcurrentHashMap (jdk1.6.0_16) 中 HashEntry 的上述 java 文档吗?为什么需要额外的预防性锁定?
- What puzzles me is this.
Java doc of HashEntry in ConcurrentHashMap (jdk1.6.0_16)
...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.
here is the implementation of get method of ConcurrentHashMap#Segment
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; }
And of readValueUnderLock
V readValueUnderLock(HashEntry e) {
lock();
try {
return e.value;
} finally {
unlock();
}
}
Based of my reading and understanding every thread will read an up to date value of volatile variable.
So when will a thread read initial null value? especially in HashEntry where value is assigned before the constructor completes. (Also note that HashEntry's reference never escapes its constructor.)
I am stumped, can some one explain the above java doc of HashEntry in ConcurrentHashMap (jdk1.6.0_16). and why that extra precaution locking is required?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
Java 1.5发布的时候,JMM中有一个规定,说HashEntry可以部分初始化。也就是说,当线程放入映射中时,会创建 HashEntry 并将其分配为对存储桶头或碰撞成员的引用。此时条目的值可能还没有被分配以被其他线程看到。
CHM 假定如果条目不为 null,则该值不应为 null,因此将 readValueUnderLock 放入作为故障保护。
我向 DL 询问了这个具体情况,他说尽管有可能发生,但它永远不应该发生。他还表示,从1.6开始,这个问题就不会发生了。
When Java 1.5 was released, there was a provision in the JMM that said that the HashEntry can be partially initialized. That is, when a thread is putting into the map, the HashEntry is created and assigned as a reference to either the bucket head or a collison member. At that time the value of the entry, may have not been assigned to be seen by the other threads.
The CHM assumes that if the entry isnt null, then the value shouldn't be null so readValueUnderLock was put in as a failsafe.
I asked DL about this exact situation and he said that despite the possibility of it happening, it never should. He also said that since 1.6, that issue won't happen.
您必须确保在构造函数完成之前,任何人都不能使用对 Map 的引用。当它是私有字段并且只能通过 getter 方法访问时(包括在保存该字段的类中),这应该很容易做到。
构造函数将在实例 getter 方法被调用之前完成。
You must make sure that the reference to the Map cannot be used by anyone, before the constructor completes. That should be easy to do when it is a private field and only accesses by getter methods - including in the class where that field is held.
The constructor will finish before the instance getter method will be able to be called.