ConcurrentHashMap и числа Фибоначчи - непоследовательный результат

Я написал программу для вычисления чисел фибоначчи рекурсивно с помощью метода ConcurrentHashMap и computeIfAbsent():

Программа работает абсолютно нормально, когда я использовал небольшие значения, такие как 8,9,10, но застрял в бесконечном цикле, когда значение, увеличенное с 10 to 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);
        });
    }
}

Может кто-нибудь сказать мне, почему он застрял навсегда?

Ответы

Ответ 1

Вы зашли в тупик.

computeIfAbsent на ConcurrentHashMap заблокирует ведро, в которое будет идти соответствующий ключ. Если вы пытаетесь вычислить Fibonacci, который больше, чем количество ковшей на вашей карте, тогда рекурсивные вызовы будут пытаться заблокировать ведро, которое уже заблокировано дальше в стеке вызовов. Но, конечно, эта блокировка не может быть выпущена до тех пор, пока все рекурсивные вызовы не будут завершены. Таким образом, ваш тупик.

Я бы пересмотрел ваше решение использовать ConcurrentHashMap здесь.

Ответ 2

Этот метод рекурсии для вычисления числа фибонаксов имеет экспоненциальную сложность. С кэшированием вы уменьшаете его до линейного, или вы можете использовать простой цикл вместо рекурсии для получения линейного алгоритма.

Интересно, почему вы используете ConcurentHashMap для кеширования. Я бы использовал либо простую карту, либо массив для кэширования.

Карты имеют преимущество против массивов, когда значения являются разреженными, но когда у вас есть последовательность чисел, вы можете использовать простой массив.

Ответ 3

Я взял дамп потока, и мы видим, что поток с блокировкой 0x000000076b70bba0 вызывает проблему с мертвой блокировкой.

Пожалуйста, исправьте меня, если я ошибаюсь.

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

Ответ 4

В соответствии с Oracle Doc

Некоторые попытки выполнить операции обновления на этой карте другими потоками могут быть заблокированы, пока выполняется вычисление, поэтому вычисление должно быть коротким и простым и не должно пытаться обновлять любые другие сопоставления этой карты

Как справедливо сказано Joe C в самом верхнем ответе, инициализация по умолчанию ConcurrentHashMap имеет небольшое количество ведер, выделенных во время создания экземпляра.

Цель использования ConcurrentHashMap - разрешить одновременную модификацию Карты из нескольких потоков без необходимости их блокировки (ссылка).


Если вы все еще хотите остаться с помощью ConcurrentHashMap для своего приложения, я бы рекомендовал увеличить начальную емкость при ее создании.

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

может рассчитать ряд Фибоначчи и включить 25


В соответствии с документом

ConcurrentHashMap()
Создает новую пустую карту с начальным размером таблицы по умолчанию (16).

Тем не менее, я прошу разницы в этом, поскольку я заметил, что размер по умолчанию намного меньше.
Причина этого в том, что вы получаете fibonacci(25) от ConcurrentHashMap<>(11), тогда ConcurrentHashMap<>() < - должно быть по умолчанию здесь 16, но это не так.