2017-05-04 10 views
15

Napisałem program do obliczania liczby Fibonacci rekurencyjnie, z ConcurrentHashMap i computeIfAbsent() metody:ConcurrentHashMap i Fibonacciego Liczby - Niezgodne wynik

program działa absolutnie bez zarzutu, gdy użyłem małych wartości jak 8,9,10 ale utknęła w niekończącej się pętli, gdy wartość wzrosła z 10 to 20program nie zatrzymuje

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); 
     }); 
    } 
} 

Czy ktoś mi powiedzieć, dlaczego jest tkwić wiecznie?

+0

Masz wyjaśnienie poniżej, ale to, co powiedziałem o rekurencyjnym Fibonacciego jest ważne; używaj programowania dynamicznego, jeśli naprawdę potrzebujesz generować sekwencje o wysokiej sekwencji Fibonacciego. –

+0

@ TimBiegeleisen- Tak, będę .. ja po prostu bawiłem się z równoczesną mapą skrótu i ​​znalazłem to ... :) –

+1

@TimBiegeleisen OP robił dynamiczne programowanie, tylko w nie tak oczywisty sposób. Każdy termin liczby fibonacci jest obliczany tylko wtedy, gdy nie został wcześniej obliczony. Jeśli został wcześniej obliczony, wartość jest porównywana z 'concurrentMap' –

Odpowiedz

27

Uderzasz w zakleszczenie.

computeIfAbsent na ConcurrentHashMap zablokuje wiadro, w którym znajduje się odpowiedni klucz. Jeśli próbujesz obliczyć wartość Fibonacci wyższą niż liczba segmentów na mapie, połączenia rekursywne będą próbowały zablokować zasobnik, który jest już zablokowany na stosie wywołań. Ale oczywiście tej blokady nie można zwolnić, dopóki wszystkie rekurencyjne połączenia nie zostaną zakończone. Zatem twój impas.

Chciałbym ponownie rozważyć decyzję o użyciu tutaj ConcurrentHashMap.

+0

Myślę, że to jest poprawna odpowiedź. Próbowałem tego samego kodu z 'HashMap' i działa on dla wartości większych niż 20. Z drugiej strony, dla' ConcurrentHashMap' kod zakleszcza się, gdy i> = 14. – Eran

+0

@Eran Zgadzam się z Tobą. Odpowiedziałem na to, że wiem, że rekurencyjne Fibonacciego zawodzi w przypadku stosunkowo niewielkich liczb, nawet bez wzięcia pod uwagę, że 'ConcurrentHashMap' był używany +1 –

+4

Może warto połączyć się z [JavaDocs z ConcurrentHashMap.computeIfAbsent] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#computeIfAbsent-K-java.util.function.Function-): "Niektóre usiłowania aktualizacji na tej mapie przez inne wątki mogą być blokowane podczas wykonywania obliczeń, więc obliczenia powinny być krótkie i proste, i nie wolno próbować aktualizować żadnych innych mapowań tej mapy. " – Hulk

3

Ta metoda rekursji do obliczania liczby fibonaci ma wykładniczą złożoność. Przy buforowaniu zmniejszasz ją z powrotem do liniowości lub możesz użyć prostego cyklu zamiast rekursji, aby uzyskać liniowy algorytm.

Zastanawiam się, dlaczego używasz ConcurentHashMap do buforowania. Chciałbym użyć albo zwykłej mapy, albo tablicy do buforowania.

Mapy mają przewagę nad tablicami, gdy wartości są sparowane, ale jeśli masz sekwencję liczb, możesz użyć prostej tablicy.

3

Zrobiłem zrzut wątku i widzimy, że wątek z blokadą 0x000000076b70bba0 powoduje problem martwej blokady.

Proszę mnie poprawić, jeśli się mylę.

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 
+0

W rzeczy samej - i linia 1674 z ConcurrentHashMap.java (w moim jdk1.8.0_121) jest zsynchronizowana (f) {', gdzie' f' jest 'węzłem ', w tym przypadku' ReservationNode' - prawdopodobnie ten sam blokowany na samym dole stosu, zgodnie z hashe. – Hulk

+2

... wszystkie są oczywiście szczegółami dotyczącymi implementacji i mogą się zmienić w dowolnym momencie. Ważną rzeczą jest umowa ConcurrentHashMap.computeIfAbsent, która stwierdza, że ​​funkcja mapowania "nie może próbować aktualizować żadnych innych odwzorowań tej mapy". (zobacz także mój drugi komentarz https://stackoverflow.com/questions/43774947/concurrenthashmap-and-fibonacci-number-inconsistent-result#comment74592799_43775058) – Hulk

1

Jak na Oracle Doc

Kilka prób operacji aktualizacji na tej mapie przez innych wątków może być zablokowana podczas obliczeń jest w toku, więc obliczenia powinny być krótkie i proste, i nie należy próbować aktualizować innych mapowania tej mapie

Jak słusznie powiedział przez Joe C w najwyższej odpowiedź, domyślna inicjalizacja ConcurrentHashMap ma SMA Liczba łyżek przydzielonych w momencie tworzenia.

Celem użycia ConcurrentHashMap jest umożliwienie równoczesnej modyfikacji mapy z kilku wątków bez konieczności ich blokowania (link).


Jeśli nadal chcą pozostać z użyciem ConcurrentHashMap dla danej aplikacji, to polecam zwiększenie initialCapacity podczas jego tworzenia.

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

może obliczyć szereg Fibonacciego do września włącznie 25


Jak na doc,

ConcurrentHashMap()
Tworzy nową, pustą mapę z domyślnym początkowego rozmiaru tabeli (16).

Jednak proszę się różnić, ponieważ zauważyłem, że domyślny rozmiar jest znacznie mniejszy.
Powodem tego jest, gdy masz fibonacci(25) z ConcurrentHashMap<>(11), a następnie ConcurrentHashMap<>() < - powinno być domyślnie 16 tutaj .., ale nie jest.

+0

Tutaj są 2 nieporozumienia: 1. Domyślna początkowa pojemność 'ConcurrentHashMap' ma 16, jak stwierdzono w JavaDocs. Jednak domyślny "loadFactor" wynosi 0,75, więc gdy tylko umieścisz 13 element na mapie, spróbuje on się rozwinąć. – Hulk

+0

2.Tylko dlatego, że twoja mapa ma pojemność 16, nie oznacza to, że umieszczenie w niej 16 wpisów umieści je w oddzielnych wiadrach. – Hulk

+0

Wystarczy przykład: Wypełnij 'HashMap' klawiszami' k = [0,1,2,4 , 9,16,25..100] '(wartości nie mają znaczenia), a za pomocą debugera można zweryfikować, że tylko 4 z 16 domyślnych segmentów zawiera wartości, a w indeksach: 0, 1, 4 , 9. Wiadro w indeksie 0 zawiera odwzorowania dla klawiszy 0,16 i 64, a w indeksie 1 zawiera odwzorowania dla 1, 49 i 81. – Hulk

Powiązane problemy