- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
在深入分析ConcurrentHashMap
时,发现网上有一篇博文说ConcurrentHashMap
也有可能陷入死循环。
它给出了这个例子。当我运行这段代码时 - 它卡住了:
public class Test {
public static void main(String[] args) throws Exception {
Map<Long, Long> map = new ConcurrentHashMap<>();
map.put(0L, 0L);
map.put((1L << 32) + 1, 0L);
for (long key : map.keySet()) {
map.put(key, map.remove(key));
}
}
}
请解释为什么会出现这种死锁。
最佳答案
正如其他人已经说过的:这不是死锁,而是死循环。不管怎样,问题的核心(和标题)是:为什么会这样?
其他答案在这里没有详细介绍,但我也很想更好地理解这一点。例如,当您更改行时
map.put((1L << 32) + 1, 0L);
到
map.put(1L, 0L);
然后它不会卡住。同样,问题是为什么。
答案是:这很复杂。
ConcurrentHashMap
是并发/集合框架中最复杂的类之一,高达 6300 行代码,230 行注释仅解释了实现的基本概念,以及为什么神奇且不可读的代码实际上有效。以下内容相当简单,但至少应该解释基本问题。
首先: Map::keySet
返回的集合是内部状态的 View 。 JavaDoc 说:
Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. The set supports element removal, [...]
(我强调)
然而, ConcurrentHashMap::keySet
的 JavaDoc说:
Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. The set supports element removal, [...]
(注意它没有提到未定义的行为!)
通常,在遍历 keySet
时修改 map 会抛出 ConcurrentModificationException
.但是 ConcurrentHashMap
能够应付这个。它保持一致并且仍然可以迭代,即使结果可能仍然出乎意料 - 就像您的情况一样。
得出您观察到的行为的原因:
A hash table (or hash map)基本上是通过计算键的哈希值,并将此键用作应将条目添加到的“桶”的指示器来工作的。当多个键映射到同一个桶时,桶中的条目通常被管理为一个链表。 ConcurrentHashMap
也是如此.
以下程序在迭代和修改期间使用一些讨厌的反射 hack 来打印表的内部状态 - 特别是表的“桶”,由节点组成:
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MapLoop
{
public static void main(String[] args) throws Exception
{
runTestInfinite();
runTestFinite();
}
private static void runTestInfinite() throws Exception
{
System.out.println("Running test with inifinite loop");
Map<Long, Long> map = new ConcurrentHashMap<>();
map.put(0L, 0L);
map.put((1L << 32) + 1, 0L);
int counter = 0;
for (long key : map.keySet())
{
map.put(key, map.remove(key));
System.out.println("Infinite, counter is "+counter);
printTable(map);
counter++;
if (counter == 10)
{
System.out.println("Bailing out...");
break;
}
}
System.out.println("Running test with inifinite loop DONE");
}
private static void runTestFinite() throws Exception
{
System.out.println("Running test with finite loop");
Map<Long, Long> map = new ConcurrentHashMap<>();
map.put(0L, 0L);
map.put(1L, 0L);
int counter = 0;
for (long key : map.keySet())
{
map.put(key, map.remove(key));
System.out.println("Finite, counter is "+counter);
printTable(map);
counter++;
}
System.out.println("Running test with finite loop DONE");
}
private static void printTable(Map<Long, Long> map) throws Exception
{
// Hack, to illustrate the issue here:
System.out.println("Table now: ");
Field fTable = ConcurrentHashMap.class.getDeclaredField("table");
fTable.setAccessible(true);
Object t = fTable.get(map);
int n = Array.getLength(t);
for (int i = 0; i < n; i++)
{
Object node = Array.get(t, i);
printNode(i, node);
}
}
private static void printNode(int index, Object node) throws Exception
{
if (node == null)
{
System.out.println("at " + index + ": null");
return;
}
// Hack, to illustrate the issue here:
Class<?> c =
Class.forName("java.util.concurrent.ConcurrentHashMap$Node");
Field fHash = c.getDeclaredField("hash");
fHash.setAccessible(true);
Field fKey = c.getDeclaredField("key");
fKey.setAccessible(true);
Field fVal = c.getDeclaredField("val");
fVal.setAccessible(true);
Field fNext = c.getDeclaredField("next");
fNext.setAccessible(true);
System.out.println(" at " + index + ":");
System.out.println(" hash " + fHash.getInt(node));
System.out.println(" key " + fKey.get(node));
System.out.println(" val " + fVal.get(node));
System.out.println(" next " + fNext.get(node));
}
}
runTestInfinite
的输出案例如下(省略多余部分):
Running test with infinite loop
Infinite, counter is 0
Table now:
at 0:
hash 0
key 4294967297
val 0
next 0=0
at 1: null
at 2: null
...
at 14: null
at 15: null
Infinite, counter is 1
Table now:
at 0:
hash 0
key 0
val 0
next 4294967297=0
at 1: null
at 2: null
...
at 14: null
at 15: null
Infinite, counter is 2
Table now:
at 0:
hash 0
key 4294967297
val 0
next 0=0
at 1: null
at 2: null
...
at 14: null
at 15: null
Infinite, counter is 3
...
Infinite, counter is 9
...
Bailing out...
Running test with infinite loop DONE
可以看到关键字 0
的条目和 key 4294967297
(这是你的 (1L << 32) + 1
)总是以桶 0 结尾,并且它们作为链表进行维护。所以对 keySet
的迭代从这张表开始:
Bucket : Contents
0 : 0 --> 4294967297
1 : null
... : ...
15 : null
在第一次迭代中,它删除了键 0
,基本上把 table 变成了这个:
Bucket : Contents
0 : 4294967297
1 : null
... : ...
15 : null
但是关键0
之后立即添加,并在与 4294967297
相同的桶中结束- 所以它被附加在列表的末尾:
Bucket : Contents
0 : 4294967297 -> 0
1 : null
... : ...
15 : null
(这由输出的 next 0=0
部分指示)。
在下一次迭代中,4294967297
被删除并重新插入,使表进入与最初相同的状态。
这就是无限循环的由来。
与此相反,runTestFinite
的输出案例是这样的:
Running test with finite loop
Finite, counter is 0
Table now:
at 0:
hash 0
key 0
val 0
next null
at 1:
hash 1
key 1
val 0
next null
at 2: null
...
at 14: null
at 15: null
Finite, counter is 1
Table now:
at 0:
hash 0
key 0
val 0
next null
at 1:
hash 1
key 1
val 0
next null
at 2: null
...
at 14: null
at 15: null
Running test with finite loop DONE
可以看到键0
和 1
最终进入不同的桶。因此,不存在可以追加删除(和添加)元素的链表,循环在遍历相关元素(即前两个桶)一次后终止。
关于java - ConcurrentHashMap 陷入无限循环 - 为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56896955/
我最初构造了一系列嵌套的 ConcurrentHashMaps private final ConcurrentHashMap allInOne = new ConcurrentHashMap
我正在尝试使用 ConcurrentHashMap 初始化 ConcurrentHashMap private final ConcurrentHashMap > myMulitiConcurrent
为了提高工作效率,我尝试将数据保存在一个动态容器中。 我在 class 中初始化它与 private final ConcurrentHashMap allInOne = new Concur
我正在创建基于 Socket 的服务器-客户端预订服务,并且遇到有关将由多个线程访问的类的问题,是否需要扩展 ConcurrentHashMap 或创建变量 ConcurrentHashMap 是否足
从 Javadoc 我知道 ConcurrentHashMap.replace 是原子的,但是 ConcurrentHashMap.put 呢?我看到它们在源代码中的实现方式不同,但我无法弄清楚它们的
是 ConcurrentHashMap.get() 保证看到以前的ConcurrentHashMap.put()通过不同的线程?我的期望是,阅读 JavaDocs 似乎表明了这一点,但我 99% 确信
使用 ConcurrentHashMap,我发现 computeIfAbsent 比 putIfAbsent 慢两倍。这里是简单的测试: import java.util.ArrayList; imp
我有一个以下格式的 ConcurrentHashMap: ConcurrentHashMap> 现在在此 map 中,我想删除数组列表中的特定值。任何人都可以指导这一点。 编辑1:我有一张 map >
为什么 ConcurrentHashMap.Segment 和 ConcurrentHashMap.HashEntry 类是静态的?为什么要这样设计? 最佳答案 基本上所有不需要使用其封闭类属性的内部
在 ConcurrentHashMap 中通过键递增并发计数器时,使用常规 Int 作为值是否安全,还是我们必须使用 AtomicInteger?例如考虑以下两个实现 ConcurrentHashMa
我对java中的并发数据结构有疑问,特别是: 1) ConcurrentHashMap 2) HashMap 3) ConcurrentHashMap 如果我理解正确的话: 1) 读/写是线程安全的,
我正在尝试查看实际的 Java 文档,描述传递给 ConcurrentHashMap.computeIfAbsent 和 ConcurrentHashMap.computeIfPresent< 时可以
我有一个名为 SerializableL 的接口(interface)由 3 个不同的类实现: 产品 横幅 标签 我开始重构,想用多个方法调用替换多个段落。 public void load(Conc
一 JDK 中的 ConcurrentHashMap 在 JDK 8以前,HashMap 是基于数组 + 链表来实现的。整体上看,HashMap 是一个数组,但每个数组元素又是一张链表。 当向 Has
我想知道当我们在调整大小时尝试读取 ConcurrentHashMap 时可能发生的情况。 我知道在读取期间,第一次尝试总是不同步的。在第二次尝试中,它将尝试获取锁并重试。 但是,如果它在调整大小时发
在一个应用程序中,1 个线程负责不断更新映射,主线程定期读取映射,使用 ConcurrentHashmap 是否足够?或者我应该明确地锁定同步块(synchronized block)中的操作吗?任何
介绍 ConcurrentHashMap 技术是为了解决问题而生的,ConcurrentHashMap 解决了多个线程同时操作一个 HashMap 时,可能出现的内部问题。当多个线程同时操作一
我有一个由多个线程访问的键值映射: private final ConcurrentMap key_vval_map = new ConcurrentHashMap(); 我的自定义 get() 和
谁能告诉我这段代码出了什么问题?我要拔头发了! 如果我使用 HashMap 而不是 ConcurrentHashMap 则没有任何问题。代码使用JDK 5.0编译 public class MapTe
来自 ConcurrentHashMap 的源码 /** 171 * Number of unsynchronized retries in size and containsVal
我是一名优秀的程序员,十分优秀!