ConcurrentHashMap重新排序指令?

时间:2011-02-15 10:23:29

标签: java multithreading concurrency

我正在调查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);   ?

3 个答案:

答案 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的最后一次赋值之前,无法将对象赋值给任何内容(以确保正确的排序也称为之前发生过)