ConcurrentHashMap中的HashEntry Java文档(jdk1.6.0_16)
...因为值字段是易失性的,而不是最终的,所以对于非同步读取器而言,Java内存模型在通过数据争用读取时看到null而不是初始值是合法的。尽管实际上不太可能发生重新排序,但是如果在非同步访问方法中看到null(预初始化)值,则Segment.readValueUnderLock方法将用作备份。
这里是ConcurrentHashMap#Segment
的get方法的实现
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;
}
和readValueUnderLock
V readValueUnderLock(HashEntry e) {
lock();
try {
return e.value;
} finally {
unlock();
}
}
根据我的阅读和理解,每个线程都会读取volatile变量的最新值。
那么线程何时会读取初始空值?特别是在HashEntry中,在构造函数完成之前赋值。 (另请注意,HashEntry的引用永远不会转义其构造函数。)
我很难过,有人可以在ConcurrentHashMap(jdk1.6.0_16)中解释上面的HashEntry java doc。为什么需要额外的预防措施锁定?
答案 0 :(得分:3)
当Java 1.5发布时,JMM中有一条规定可以部分初始化HashEntry。也就是说,当线程放入地图时,会创建HashEntry并将其指定为对桶头或collison成员的引用。此时,条目的值可能尚未分配给其他线程。
CHM假设如果条目不为null,则该值不应为null,因此readValueUnderLock作为故障保护放入。
我向DL询问了这个确切的情况,他说虽然有可能发生这种情况,但它永远不应该。他还说,自1.6以来,这个问题不会发生。
答案 1 :(得分:0)
在构造函数完成之前,您必须确保任何人都无法使用对Map的引用。当它是一个私有字段并且只能通过getter方法访问时应该很容易做到 - 包括在该字段所在的类中。
构造函数将在实例getter方法被调用之前完成。