【问题标题】:Maybe I've found a bug of concurrenthashmap也许我发现了 concurrenthashmap 的一个错误
【发布时间】:2017-05-16 12:16:06
【问题描述】:

当我想高效实现a^b时,我使用并发hashmap来存储计算值,代码是

private static final ConcurrentHashMap<String,Long> cache = new ConcurrentHashMap();

public long pow(long a, long b){
    System.out.printf("%d ^ %d%n",a,b);
    if(b == 1L){
        return a;
    }
    if( b == 2L){
        return a*a;
    }
    long l = b/2;
    long r = b - l;

    return cache.computeIfAbsent(a+","+l,k->pow(a,l)) * cache.computeIfAbsent(a+","+r,k->pow(a,r));
}

那我调用这个方法

pow(2, 30);

但输出后

2 ^ 30
2 ^ 15
2 ^ 7

它被阻止了,通过使用jstack -l pid我得到了以下信息

"main" #1 prio=5 os_prio=31 tid=0x00007f910e801800 nid=0x1703 runnable [0x0000700000217000]
   java.lang.Thread.State: RUNNABLE
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1718)
    at interview.Pow.pow(Pow.java:28)
    at interview.Pow.lambda$pow$0(Pow.java:28)
    at interview.Pow$$Lambda$1/1807837413.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b72d930> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at interview.Pow.pow(Pow.java:28)
    at interview.Pow.lambda$pow$0(Pow.java:28)
    at interview.Pow$$Lambda$1/1807837413.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b72d060> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at interview.Pow.pow(Pow.java:28)
    at interview.Pow.testPow(Pow.java:32)

一开始我怀疑是不是死锁了,后来追查ConcurrentHashmap的源码,才知道原来是死循环。 当key为2,3时,与key2,15具有相同的索引9,但fh(fh = f.hash)为-3,不能满足

if (fh >= 0) {...}

所以在这种情况下,它不能打破循环

for (Node<K,V>[] tab = table;;) {...}

然后无限循环。

是bug还是故意设计的?

【问题讨论】:

  • 您在地图上运行computeIfAbsent 时递归调用pow。因此,您有两次尝试写入地图。

标签: java-8 concurrenthashmap


【解决方案1】:

正如 C-Otto 已经评论的那样,您在第一个 computeIfabsent() 方法调用中调用了第二个 computeIfAbsent()The documentation for this method 明确表示:

其他线程在此映射上的某些尝试更新操作可能会在计算进行时被阻止,因此计算应该简短而简单,并且不得尝试更新此映射的任何其他映射。

所以这不是 ConcurrentHashMap 实现中的错误,而是在您的代码中。

【讨论】:

  • 是的,ConcurrentHashMap 只是为并发使用而不是递归使用而设计的,因此如果知道没有并发操作,则应将其更改为使用普通 HashMap。但可能在一些罕见的场景中同时存在并发操作和递归操作。那么在这种情况下我应该使用什么地图? Collections.synchronizedMap()?
猜你喜欢
  • 2016-11-05
  • 2011-04-01
  • 1970-01-01
  • 2012-11-15
  • 2014-02-19
  • 2020-02-17
  • 1970-01-01
  • 2017-07-05
  • 2011-07-13
相关资源
最近更新 更多