2017-05-04 10 views
15

私はConcurrentHashMapcomputeIfAbsent()方法で、再帰的にフィボナッチ数を計算するプログラムを書きました:私は8,9,10のような小さな値を使用するが、値が上昇したときに無限ループに陥ったときにのConcurrentHashMapとフィボナッチ数 - 一貫性のない結果

プログラムは絶対に正常に動作しますそれは永遠に立ち往生されている理由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); 
     }); 
    } 
} 

を停止したことがないいくつかのいずれかを教えてもらえますか?

+0

あなたは以下の説明をしていますが、再帰フィボナッチについて私が言ったことは有効です。あなたが本当に高いシーケンスフィボナッチ数を生成する必要があるならば、ダイナミックプログラミングを使用してください。 –

+0

@ TimBiegeleisen-はい私は..私はちょうど同時ハッシュマップで遊んでいて、これを見つけました... :) –

+1

@TimBiegeleisen OPは動的プログラミングをしていましたが、それほど明白ではありません。フィボナッチ数の各項は、以前に計算されていない場合にのみ計算されます。 preivously計算されていた場合、値は 'concurrentMap'から参照されます –

答えて

27

デッドロックが発生しています。

computeIfAbsent(コンカレントハッシュマップ)は、対応するキーが移動するバケットをロックします。マップ内のバケットの数よりも高いフィボナッチを計算しようとすると、再帰呼び出しはすでに呼び出しスタックの上にロックされているバケットをロックしようとします。もちろん、すべての再帰呼び出しが完了するまでロックを解放することはできません。したがって、あなたのデッドロック。

ここでConcurrentHashMapを使用することを決定しました。

+0

これは正しい答えだと思います。私は 'HashMap'で同じコードを試して、20より大きい値で動作します。一方、' ConcurrentHashMap'では、i> = 14のときコードがデッドロックします。 – Eran

+0

@Eran私はあなたに同意します。 「ConcurrentHashMap」が使用されていることを考慮せずに、再帰的なフィボナッチが比較的少ない数で失敗することを知っていることに基づいて私の答えを爆発させた+1 –

+4

[JavaDocs ConcurrentHashMap.computeIfAbsent](https: /docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#computeIfAbsent-K-java.util.function.Function-): "一部のユーザーは、このマップ上の他のスレッドによる更新操作を試みました計算が進行中にブロックされる可能性があるので、計算は短く単純でなければならず、このマップの他のマッピングを更新しようとしてはなりません。 – Hulk

3

フィボナッチ数を計算するためのこの再帰方法は、指数関数的な複雑さを持っています。キャッシングを使用すると、線形に戻すことができます。また、線形アルゴリズムを得るために、再帰の代わりに単純なサイクルを使用することもできます。

キャッシングになぜ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 
+0

ConcurrentHashMap.javaの行1674(my jdk1.8.0_121内)は 'synchronized(f){'、 'f'は' Node '、この場合は' ReservationNode' - ハッシュによればスタックの一番下にロックされているものと思われます。 – Hulk

+2

...これらはすべて実装の詳細であり、いつでも変更される可能性があります。重要なのは、ConcurrentHashMap.computeIfAbsentの規約で、マッピング関数は「このマップの他のマッピングを更新してはいけません」と述べています。 (私の他のコメントも参照してくださいhttps://stackoverflow.com/questions/43774947/concurrenthashmap-and-fibonacci-number-inconsistent-result#comment74592799_43775058) – Hulk

1

演算中は他のスレッドによってこのマップ上のいくつか試みた更新操作がブロックされることがあり、その計算は短く、シンプルであるべき、としないでなければなりませんOracle Doc

を1としてこのマップの他のマッピングを更新

は当然ジョー・C最上位の答えでで言ったように、ConcurrentHashMapのデフォルトの初期化は、SMAを持って インスタンス化時に割り当てられたバケット数。

ConcurrentHashMapを使用する目的は、それらをブロックする必要なく複数のスレッドからマップを同時に変更できるようにすることです(link)。


あなたはまだあなたのアプリケーションのためのConcurrentHashMapを使用して滞在したい場合は、私は、その作成中InitialCapacityの値を増やすことをお勧めします。

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

は件までフィボナッチ数列を計算し、DOC 1として25


含む、

できConcurrentHashMapの()
は、デフォルトの初期テーブルサイズで、新しい空のマップを作成します(16)。

しかし、デフォルトサイズがはるかに小さいことに気付いたので、私はこれを尋ねます。
fibonacci(25)ConcurrentHashMap<>(11)からConcurrentHashMap<>() <に変更した場合の理由は、ここではデフォルト16にする必要がありますが、そうではありません。

+0

ここでは2つの誤解があります:1. 'ConcurrentHashMap'のデフォルトの初期容量JavaDocsに記載されているように16です。しかし、デフォルトの 'loadFactor'は0.75です。したがって、13番目の要素をマップに配置するとすぐに、マップは拡大しようとします。 – Hulk

+0

2。あなたのマップが16の容量を持っているからといって、16個のエントリを入れてバケットに入れることは意味しません。 – Hulk

+0

ちょうど例です: 'HashMap'を' k = [0,1,2,4 、9,16,25..100] '(値は関係ありません)、デバッガの助けを借りて、16個のデフォルトバケットのうち4個だけが値0,1,4インデックス0のバケットには、キー0,16および64のマッピングが含まれ、インデックス1のバケットは、1,49および81のマッピングに対応します。 – Hulk

関連する問題