【问题标题】:ConcurrentHashMap in JDK7 code explanation (scanAndLockForPut)JDK7代码解释中的ConcurrentHashMap(scanAndLockForPut)
【发布时间】:2014-10-01 12:59:08
【问题描述】:

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 条目:

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

我的问题是: 为什么一定要“(retries & 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 次更改一次。

上面的解释对吗?

【问题讨论】:

  • 不,你的解释不正确。拥有单个 CPU 内核并不意味着没有并发性。即使是单核机器也可以进行抢先式多任务处理。根据核心数量做出任何假设都是错误的。顺便说一下,Runtime.getRuntime().availableProcessors() 的结果是允许随时间变化的。见its JavaDoc
  • @Holger 是的!我意识到在我发布我的编辑之后,但我决定保持不变。所以如果不是解释,那又是什么呢?
  • 我只能猜测,我不想发表猜测。最后,Java 8 的实现看起来完全不同,理解这段特定的代码对我来说并不重要……

标签: concurrency java.util.concurrent concurrenthashmap


【解决方案1】:

让我们分解一下:

1:

(retries & 1) == 0

对于奇数返回 1,对于偶数返回 0。基本上,要通过,如果数字是偶数,则有二分之一的机会。

2:

f = entryForHash(this, hash)

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

3:

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

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

【讨论】:

  • 是的,我理解整个“else if”的意思。我不知道的是特定的“(重试和 1)== 0”。为什么一定要判断retries & 1 是否等于0?
  • 我相信它是用来确保最后的元素不会被遗漏的,但这是一个有根据的猜测,所以请谨慎对待
【解决方案2】:

我在并发兴趣邮件列表上问过这个问题,作者(Doug Lea)自己回答:

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

link

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

【讨论】:

    【解决方案3】:

    我认为该方法存在一些错误! 首先让我们看看 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是源头节点

    step: 3. 最后,节点成为新的头,所以最终我们放的每一个东西都会成为条目列表的头,当concurrentHashMap在并发环境中工作时,可能会在同一个条目列表中出现两个相等的条目.

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-24
      • 2011-10-25
      • 1970-01-01
      • 1970-01-01
      • 2015-03-23
      • 1970-01-01
      • 2012-07-20
      相关资源
      最近更新 更多