ConcurrentHashMap重新排序指令?

sec*_*ask 15 java concurrency multithreading

我正在研究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;
        }
Run Code Online (Sandbox Code Playgroud)

    /**
     * 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

和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;
            }
Run Code Online (Sandbox Code Playgroud)

放工具

tab[index] = new HashEntry<K,V>(key, hash, first, value);
Run Code Online (Sandbox Code Playgroud)

我混淆了HashEntry注释,作为JSR-133,一旦构造了HashEntry,所有其他线程都可以看到所有最终字段,字段是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();
Run Code Online (Sandbox Code Playgroud)

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

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

Joh*_*int 7

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

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

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

但这意味着NEW内存模型不需要"readValueUnderLock".

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

我的理解是否正确.

响应:

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

这一切都可以在这里看到