Concurrency JDK7代码解释中的ConcurrentHashMap(scanAndLockForPut)

Concurrency JDK7代码解释中的ConcurrentHashMap(scanAndLockForPut),concurrency,java.util.concurrent,concurrenthashmap,Concurrency,Java.util.concurrent,Concurrenthashmap,JDK7中ConcurrentHashMap中的scanAndLockForPut方法说明: 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 = n

JDK7中ConcurrentHashMap中的scanAndLockForPut方法说明:

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;
}
我的问题是: 为什么我们必须执行“(重试次数&1)==0”

编辑: 我有点明白了。这都是因为常量MAX_SCAN_重试:

static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
在单核处理器中,最大扫描重试次数=1。因此,当线程第二次进入循环“while(tryLock)”时,它不必检查第一个节点是否已更改

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

上面的解释正确吗?

让我们详细分析一下:

1:

奇数返回1,偶数返回0。基本上,如果这个数字是偶数,那么通过的几率是1/2

2:

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

3:


检查值是否已更改。如果它这样做了,它会将当前条目移动到起始位置,并再次迭代链接节点以尝试获取锁。

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

对。我们只需要确保陈腐最终被发现。 交替使用头部检查可以很好地工作,并简化了 单处理器和多处理器的代码相同


因此,我认为这是这个问题的结束。

我认为该方法存在一些缺陷! 首先让我们看看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;
    }
final V put(K键,int散列,V值,仅布尔值){
HashEntry节点=tryLock()?空:
scanAndLockForPut(键、哈希、值);//1.scanAndLockForPut仅返回
//null或新条目
V旧值;
试一试{
HashEntry[]tab=表;
int index=(tab.length-1)和hash;
HashEntry first=entryAt(制表符,索引);
for(HashEntry e=first;;){
如果(e!=null){
K;
如果((k=e.key)=key||
(e.hash==hash&&key.equals(k))){
oldValue=e.value;
如果(!onlyFabSent){
e、 价值=价值;
++modCount;
}
打破
}
e=e.next;
}
否则{
//2.此处节点为空或为新条目
//然后是节点。下一个是原点头部节点
如果(节点!=null)
node.setNext(第一个);
其他的
node=新的HashEntry(hash,key,value,first);
int c=计数+1;
如果(c>阈值和标签长度<最大容量)
再灰化(节点);
其他的
setEntryAt(制表符、索引、节点);//3.最后,节点变为
//新的头,所以最终
//我们放的每一件东西都会
//条目列表的头
//它可能看起来是两个相等的
//同一条目列表中的条目。
++modCount;
计数=c;
oldValue=null;
打破
}
}
}最后{
解锁();
}
返回旧值;
}
步骤:1。scanAndLockForPut仅返回null或新条目

步骤:2。节点最终会创建一个新条目,而node.next是原始头节点

步骤:3。最后,节点成为新的头,因此最终我们放置的每件东西都将成为条目列表的头,当concurrentHashMap在并发环境中工作时,它可能在同一条目列表中显示两个相等的条目


这是我的观点,我不确定这是否正确。所以我希望你们能给我一些建议,非常感谢

是的,我理解整个“如果”的意思。我不知道的是具体的“(重试次数&1)==0”。为什么我们必须确定重试次数&1是否等于0?我相信这是用来确保最后的元素不会被遗漏,这是一个有根据的猜测,所以请稍微考虑一下。不,你的解释是不正确的。拥有单个CPU核心并不意味着没有并发性。即使是单核机器也可以进行抢占式多任务处理。基于核的数量做出任何假设都是错误的。顺便说一句,
Runtime.getRuntime().availableProcessors()
的结果可以随时间而改变。看@Holger是的!我意识到在我发布我的编辑后,我决定保持不变。如果没有解释,那又怎样?我只能猜测,我不想发表猜测。最后,Java8实现看起来完全不同,理解这段代码对我来说并不重要…
(retries & 1) == 0
f = entryForHash(this, hash)
(/* ... */) != first
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;
    }