JDK7中的ConcurrentHashMap代码说明(scanAndLockForPut)

时间:2014-08-08 05:55:09

标签: concurrency java.util.concurrent concurrenthashmap

JDK7中ConcurrentHashMap中方法scanAndLockForPut的source codes表示:

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
    HashEntry<K,V> first = entryForHash(this, hash);
    HashEntry<K,V> e = first;
    HashEntry<K,V> node = null;
    int retries = -1; // negative while locating node
    while (!tryLock()) {
        HashEntry<K,V> f; // to recheck first below
        if (retries < 0) {
            if (e == null) {
                if (node == null) // speculatively create node
                    node = new HashEntry<K,V>(hash, key, value, null);
                retries = 0;
            }
            else if (key.equals(e.key))
                retries = 0;
            else
                e = e.next;
        }
        else if (++retries > MAX_SCAN_RETRIES) {
            lock();
            break;
        }
        else if ((retries & 1) == 0 &&
                 (f = entryForHash(this, hash)) != first) {
            e = first = f; // re-traverse if entry changed
            retries = -1;
        }
    }
    return node;
}

我理解代码的含义,但是如果输入代码,我不会这样做:

else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first)

我的问题是: 为什么我们要做“(重试&amp; 1)== 0”?

修改 : 我有点想通了。这都是因为常数MAX_SCAN_RETRIES:

static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;

在单核处理器中,MAX_SCAN_RETRIES = 1.因此第二次线程进入循环“while(tryLock)”时,不必检查第一个节点是否已更改。

但是,在多核处理器中,这就像检查第一个节点是否在while循环中每2次更改一样。

上述说明是否正确?

3 个答案:

答案 0 :(得分:2)

让我们打破这个:

1:

(retries & 1) == 0

对于奇数,返回1,对于偶数,返回0。基本上,要想过去,如果数字是偶数的话,那就有1比2的机会。

2:

f = entryForHash(this, hash)

f是一个临时变量,用于存储段中最新条目的值。

3:

(/* ... */) != first

检查值是否已更改。如果是,它会将当前条目移动到开始,并再次重新迭代链接的节点以尝试获取锁定。

答案 1 :(得分:0)

我在并发利益邮件列表上问了这个问题,作者(Doug Lea)自己回答说:

  

是。我们只需要确保最终检测到陈旧性。   交替检查头部检查工作正常,并简化了使用   单处理器和多处理器的代码相同。

link

所以我认为这是这个问题的结束。

答案 2 :(得分:0)

我认为该方法存在一些错误! 首先让我们看一下put方法:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
        HashEntry<K,V> node = tryLock() ? null :
            scanAndLockForPut(key, hash, value);//1. scanAndLockForPut only return
                                                //   null or a new Entry
        V oldValue;
        try {
            HashEntry<K,V>[] tab = table;
            int index = (tab.length - 1) & hash;
            HashEntry<K,V> first = entryAt(tab, index);
            for (HashEntry<K,V> e = first;;) {
                if (e != null) {
                    K k;
                    if ((k = e.key) == key ||
                        (e.hash == hash && key.equals(k))) {
                        oldValue = e.value;
                        if (!onlyIfAbsent) {
                            e.value = value;
                            ++modCount;
                        }
                        break;
                    }
                    e = e.next;
                }
                else {
                    // 2. here the node is null or a new Entry
                    //    and the node.next is the origin head node 
                    if (node != null)
                        node.setNext(first);
                    else
                        node = new HashEntry<K,V>(hash, key, value, first);
                    int c = count + 1;
                    if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                        rehash(node);
                    else
                        setEntryAt(tab, index, node);//3. finally, the node become 
                                                     // the new head,so eventually 
                                                     // every thing we put will be 
                                                     // the head of the entry list
                                                     // and it may appears two equals
                                                     // entry in the same entry list.
                    ++modCount;
                    count = c;
                    oldValue = null;
                    break;
                }
            }
        } finally {
            unlock();
        }
        return oldValue;
    }

步骤:1. scanAndLockForPut仅返回null或新的Entry。

步骤:2.节点最终是一个新的Entry,而node.next是原始头节点

步骤:3.最终,该节点成为新的头,因此最终,我们放置的所有内容都将成为条目列表的头,并且在并发环境中工作时,它可能在同一条目列表中出现两个等于条目。

这是我的看法,我不确定它是否正确。所以我希望大家都给我一些建议,非常感谢!