4
  • 让我困惑的是这个。

ConcurrentHashMap中HashEntry的Java文档(jdk1.6.0_16)

...因为值字段是易失性的,而不是最终的,所以在 Java 内存模型中,非同步读取器在通过数据竞争读取时看到 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。为什么需要额外的预防锁定?

4

2 回答 2

3

Java 1.5 发布时,JMM 中有一条规定说 HashEntry 可以部分初始化。也就是说,当线程放入映射时,会创建 HashEntry 并将其分配为对桶头或 collison 成员的引用。那时条目的值,可能还没有被分配给其他线程看到。

CHM 假设如果条目不为空,则该值不应为空,因此将 readValueUnderLock 作为故障安全放入。

我向 DL 询问了这个确切的情况,他说尽管它有可能发生,但它永远不会发生。他还说,从 1.6 开始,这个问题就不会发生了。

于 2010-12-03T15:23:12.577 回答
0

在构造函数完成之前,您必须确保对 Map 的引用不能被任何人使用。当它是一个私有字段并且只能通过 getter 方法访问时,这应该很容易做到 - 包括在持有该字段的类中。

构造函数将在实例 getter 方法能够被调用之前完成。

于 2010-12-03T07:50:47.850 回答