大家可能都听过 JDK7 中的 HashMap 在多线程环境下可能造成 CPU 100%的现象,这个由于在扩容的时候 put 时产生了死链,由此会在 get 时造成了 CPU 100%。这个问题在 JDK8 中的 HashMap 获得了解决。其实 JDK7 中的 HashMap 在多线程环境下不止只有 CPU 100%这一共怪异现象,它还可能造成插入的数据丢失,有兴趣的读者可以自行了解下。
对于 HashMap 多线程的问题,我们通常会这么反问:HashMap 设计上就不是多线程安全的,何必要去在多线程环境下用呢?的确如此,我们不会傻到显式的在多线程环境下调用,但是又可能在你所关注的视角范围外是多线程的,其隐式地让 HashMap 置于多线程环境下了,这个又难以一下子察觉到。再者,对于 HashMap 多线程的问题,我们很多时候推荐使用 ConcurrentHashMap 来代替 HashMap 应用于多线程的环境,很不巧的是 ConcurrentHashMap 也有可能会造成 CPU 100%的异常现象。这个怪异现象存在于 JDK8 的 ConcurrentHashMap 中,在 JDK9 中已经得到修复,可以参见: https://bugs.openjdk.java.net/browse/JDK-8062841
什么情况下 JDK8 的 ConcurrentHashMap 会出现这个 Bug 呢?首先我们来运行一下这段代码:
Map<String, String> map = new ConcurrentHashMap<>();
map.computeIfAbsent("AaAa",
key -> map.computeIfAbsent("BBBB", key2 -> "value"));
你会惊奇的发现这个程序一直处于 Running 状态,我们通过 top -Hp [pid]命令查看到其中一个线程的 CPU 使用率接近 100%,参考下图:
可以看到 pid 为 31417 的东东,我们再通过 jstack -l [pid]命令查看到对应的线程为:
注意将 nid=0x7ab9 的 16 进制转为 10 进制就是 31417。可以看到问题是发生在了 computeIfAbsent 方法中,我们将示例中的程序换成下面这段程序也会同样出现 CPU 100%的 Bug:
map.computeIfAbsent("AaAa",
(String key) -> {
map.put("BBBB", "value");
return "value";
});
问题的关键在于递归使用了 computeIfAbsent 方法,笔者在 stackoverflow 上还搜索到了同类型的问题,下面的示例程序中调用 fibonacci 方法同样也会造成 CPU 100%.
static Map<Integer, Integer> concurrentMap = new ConcurrentHashMap<>();
public static void main(String[] args) {
System.out.println("Fibonacci result for 20 is" + fibonacci(20));
}
static int fibonacci(int i) {
if (i == 0)
return i;
if (i == 1)
return 1;
return concurrentMap.computeIfAbsent(i, (key) -> {
System.out.println("Value is " + key);
return fibonacci(i - 2) + fibonacci(i - 1);
});
}
至于为什么会发生这个 BUG,答案就在 ConcurrentHashMap 中的 computeIfAbsent 方法中,自己去捞吧,嘿嘿。或者等我下一篇 怎么规避这个问题呢?只要不在递归中使用 computeIfAbsent 方法就好啦,或者降级用可爱的分段锁,或者升级 JDK9~
欢迎支持笔者新作:《深入理解 Kafka:核心设计与实践原理》和《 RabbitMQ 实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.