我正在调查ConcurrentHashMap的实现,让我感到困惑。
/* Specialized implementations of map methods */
V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K,V> 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<K,V> 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<K,V> {
final K key;
final int hash;
volatile V value;
final HashEntry<K,V> next;
HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
this.key = key;
this.hash = hash;
this.next = next;
this.value = value;
}
放置工具
tab[index] = new HashEntry<K,V>(key, hash, first, value);
我对HashEntry评论感到困惑,因为JSR-133,一旦构建了HashEntry,所有其他线程都可以看到所有最终字段, value 字段是易变的,所以我认为它可见其他线程也是??? 。另外一点,就是他说的重新排序是:HashEntry对象引用可以在完全构造之前分配给tab [...](因此结果是其他线程可以看到这个条目,但e.value可以为null)?
更新 我看过this文章,这很好。但是我需要关心像这样的案例
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); ?
答案 0 :(得分:7)
对于那些对Doug Lea关于这个主题的回答感兴趣的人,他最近解释了readValueUnderLock
的原因
这是对有问题的人的回应:
在ConcurrentHashMap中获取 方法不需要 “readValueUnderLock”因为一场比赛 remove不会使值为null。 该值永远不会为空 从删除线程。这意味着 有可能获得返回 即使删除密钥值也是如此 线程(在同一个键上)有 进展到克隆点 列表的前面部分。这个 只要它是理想的就好了 效果。
但这意味着“readValueUnderLock”是 新内存模型不需要。
然而对于OLD内存模型来说 由于可能会看到值null 重新排序(很少但可能)。
我的理解是否正确。
响应:
不完全。你是对的 永远不应该被称呼。然而 JLS / JMM可以读作并非绝对 禁止被召唤 因为需要的弱点 订购决赛之间的关系 在构造函数中设置的挥发性物质(关键是 最后,价值是不稳定的),wrt 使用该条目通过线程读取 对象。 (在JMM-ese,订购 决赛的制约因素超出了 同步关系。) 这就是文档评论的问题 (粘贴在下面)是指。没有人 曾经想过任何实际的漏洞 处理器/编译器可能会找到 产生一个空值读取,它 可能可以证明不存在(和 也许有一天会有JLS / JMM修订版 将填补空白以澄清这一点), 但比尔普格曾建议我们放 无论如何这只是为了 保守地迂腐 正确。回想起来,我不是这样 确定这是一个好主意,因为它 引导人们想出异国情调 的理论。
可以全部查看here
答案 1 :(得分:1)
据我了解内存模型,保证写入volatile变量后,所有后续(由同步顺序定义)读取该变量。
但是,没有任何保证在e.value
中读取get()
后会在构造函数中写入value
(因为没有 synchronized-with 关系在这些操作之间),以便Memory Model允许这种重新排序,并且在null
值的情况下显式同步是必要的,以确保我们读取正确的值。
UPDATE:新的内存模型保证在写入volatile变量之前,其他线程在读取volatile变量之后可以看到对非易失性变量的任何写入,但反之亦然。
以下是The Java Memory Model by Jeremy Manson, William Pugh and Sarita Adve的相关摘录:
5.1.1锁定粗化。
...
所有这些只是一种迂回的说法,即访问正常变量 可以重新排序以下易失性读取或锁定获取,或前面的 volatile write 或锁定释放。这意味着可以移动正常访问 在锁定区域内,但(大部分)不在其中;
因此,可以使用构造函数中的写入volatile变量对构造对象的赋值进行重新排序,以便检查问题。
答案 2 :(得分:1)
我对HashEntry评论感到困惑,因为 JSR-133,曾经是HashEntry 建成后,所有最终的领域都将是 对所有其他线程可见,值 领域是不稳定的,所以我认为 其他线程也可见???
其他线程也会看到值但是......条目的分配(进入Object [])是在初始化AND 锁定之后完成的。
因此,如果任何线程看到null
,它将尝试读取锁定下的值。
其他一点,是他说的重新订购 是:HashEntry对象引用即可 在标签满之前分配给标签[...] 构造(所以结果是其他的 线程可以看到这个条目但是e.value 可以是null)?
不,它不能b / c存在易失性分配(value
),并且意味着必须事先设置所有其他操作(即不重新排序)。还要记住,java对象创建是2阶段,创建一个空对象w / zero / null字段(比如使用默认的c-tor),然后调用<init>
方法(它是构造函数)。在完成构造函数调用及其value
的最后一次赋值之前,无法将对象赋值给任何内容(以确保正确的排序也称为之前发生过)