Concurrency JDK7代码解释中的ConcurrentHashMap(scanAndLockForPut)
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
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;
}