关于java:ConcurrentHashMap和Fibonacci数字 – 结果不一致

ConcurrentHashMap and Fibonacci Numbers - Inconsistent result

我用ConcurrentHashMapcomputeIfAbsent()方法编写了一个递归计算斐波那契数的程序:

当我使用像8,9,10这样的小值时,程序工作得非常好,但是当值从10 to 20增加时,程序却一直处于无休止的循环中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 public class Test {
    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);
        });
    }
}

有人能告诉我为什么它永远被卡住了吗?


你陷入了僵局。

Concurrenthashmap上的computeIfAbsent将锁定相应键将转到的存储桶。如果您试图计算的fibonacci大于映射中的存储桶数,那么递归调用将尝试锁定已在调用堆栈中进一步锁定的存储桶。但是,当然,在所有递归调用完成之前,不能释放该锁。因此,你的僵局。

我会重新考虑你决定在这里使用ConcurrentHashMap


我进行了线程转储,我们可以看到锁为0x00000076b70bba0的线程导致了死锁问题。

如果我错了,请纠正我。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
main - priority:5 - threadId:0x00000000021af000 - nativeId:0x2798 - state:RUNNABLE
    stackTrace:
    java.lang.Thread.State: RUNNABLE
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1674)
    - locked <0x000000076b70bba0> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.lambda$0(Test.java:20)
    at Test$$Lambda$1/834600351.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b70c720> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.lambda$0(Test.java:20)
    at Test$$Lambda$1/834600351.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b70c5c0> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.lambda$0(Test.java:20)
    at Test$$Lambda$1/834600351.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b70c460> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.lambda$0(Test.java:20)
    at Test$$Lambda$1/834600351.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b70c300> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.lambda$0(Test.java:20)
    at Test$$Lambda$1/834600351.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b70c1a0> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.lambda$0(Test.java:20)
    at Test$$Lambda$1/834600351.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b70c040> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.lambda$0(Test.java:20)
    at Test$$Lambda$1/834600351.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b70bee0> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.lambda$0(Test.java:20)
    at Test$$Lambda$1/834600351.apply(Unknown Source)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    - locked <0x000000076b70bba0> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
    at Test.fibonacci(Test.java:18)
    at Test.main(Test.java:8)
    Locked ownable synchronizers:
    - None


这种计算斐波纳契数的递归方法具有指数复杂性。通过缓存,可以将其减少到线性,或者可以使用简单的循环而不是递归来获得线性算法。

我想知道为什么要使用ConcurrentHashMap进行缓存。我将使用简单的映射或数组进行缓存。

当值稀疏时,映射相对于数组有优势,但当您有数字序列时,可以使用简单数组。


根据Oracle文件

Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this map

正如JoeC在最前面的答案中所说的,ConcurrentHashMap的默认初始化在实例化时分配了少量的存储桶。

使用ConcurrentHashMap的目的是允许从多个线程并发修改映射,而不需要阻塞它们(link)。

如果您仍然希望在应用程序中继续使用ConcurrentHashMap,那么我建议您在创建该应用程序时增加初始容量。

1
static Map<Integer, Integer> concurrentMap = new ConcurrentHashMap<>(11);

可计算出费波纳契级数,包括25

根据DOC,

ConcurrentHashMap()
Creates a new, empty map with the default initial table size (16).

不过,我希望在这方面有所不同,因为我注意到默认大小要小得多。原因是当你从ConcurrentHashMap<>(11)得到fibonacci(25)时,那么ConcurrentHashMap<>()在这里应该是默认值16,但不是。