2016-11-25 9 views
3

私は最近ConcurrentHashMapを使用する必要があるのか​​、通常のHashMapをマルチスレッド環境で使用できるのかについて私の研究で議論しました。 HashMapsの引数は2です。ConcurrentHashMapより高速です。可能であれば、それを使用する必要があります。そして、ConcurrentModificationExceptionは、マップ上で反復処理が行われているように見えるので、「マップからPUTしGETすれば、通常のHashMapの問題は何ですか?」議論だった。Javaハッシュマップ - 複数スレッドの投入

同時のPUTアクションまたは同時のPUTとREADが例外につながる可能性があると私は考えていましたので、これを示すテストをまとめました。テストは簡単です。 10個のスレッドを作成します。各スレッドは、同じ1000個のキーと値のペアをマップに繰り返し5秒間書き込んだ後、結果のマップを出力します。

結果は実際には非常に混乱した:

Length:1299 
Errors recorded: 0 

私は、各キーと値のペアはHashMapの中でユニークだと思ったが、マップを見て、私は同じであり、複数のキーと値のペアを見つけることができます。 何らかの例外や破損したキーや値のいずれかが予想されましたが、私はこれを期待していませんでした。これはどうやって起こるのですか?

ここで私が使用したコードは、参考のために、です:あなたが直面している

public class ConcurrentErrorTest 
{ 
    static final long runtime = 5000; 
    static final AtomicInteger errCount = new AtomicInteger(); 
    static final int count = 10; 

    public static void main(String[] args) throws InterruptedException 
    { 
     List<Thread> threads = new LinkedList<>(); 
     final Map<String, Integer> map = getMap(); 

     for (int i = 0; i < count; i++) 
     { 
      Thread t = getThread(map); 
      threads.add(t); 
      t.start(); 
     } 

     for (int i = 0; i < count; i++) 
     { 
      threads.get(i).join(runtime + 1000); 
     } 

     for (String s : map.keySet()) 
     { 
      System.out.println(s + " " + map.get(s)); 
     } 
     System.out.println("Length:" + map.size()); 
     System.out.println("Errors recorded: " + errCount.get()); 
    } 

    private static Map<String, Integer> getMap() 
    { 
     Map<String, Integer> map = new HashMap<>(); 
     return map; 
    } 

    private static Map<String, Integer> getConcMap() 
    { 
     Map<String, Integer> map = new ConcurrentHashMap<>(); 
     return map; 
    } 

    private static Thread getThread(final Map<String, Integer> map) 
    { 
     return new Thread(new Runnable() { 
      @Override 
      public void run() 
      { 
       long start = System.currentTimeMillis(); 
       long now = start; 
       while (now - start < runtime) 
       { 
        try 
        { 
         for (int i = 0; i < 1000; i++) 
          map.put("i=" + i, i); 
         now = System.currentTimeMillis(); 
        } 
        catch (Exception e) 
        { 
         System.out.println("P - Error occured: " + e.toString()); 
         errCount.incrementAndGet(); 
        } 
       } 
      } 
     }); 
    } 
} 
+5

あなたのテストでは、 'HashMap'がスレッドセーフではないことが示されました。 – Kayaman

+1

適切な同期なしで複数のスレッドから使​​用する場合、例外は発生しません。あなたは壊れたデータ構造を持っていますが、不変のままであるという保証はありません。 –

+1

'ConcurrentHashMap'を使用してください。 – Antoniossss

答えて

6

TOCTTOUクラスの問題のようです。 (はい、バグのこの種のは、それが自身の名前を持って、そう頻繁に起こる:))

あなたがマップにエントリを挿入すると、少なくとも次の二つのことが起こる必要があります。

  1. キーがすでに存在するかどうかを確認してください。
  2. チェックがtrueを返した場合は、既存のエントリを更新します。存在しない場合は、新しいエントリを追加します。

これらの2つが(正しく同期化されたマップの実装と同じように)原子的に発生しない場合、手順1でキーが存在しないと結論づけることができます彼らはステップ2に行きます、それはもはや真実ではありません。したがって、複数のスレッドが同じキーを持つエントリをうれしく挿入します。

これは唯一の問題ではないことに注意してください。実装と可視性のおかげで、さまざまな予期しないエラーが発生する可能性があります。

+3

"check-then-act"バグといっしょに発音されます。多くの場合、ロックフリーコードが発生します。 –

+0

ありがとうございます。この回答と、以前にリンクした[ブログの投稿](http://mailinator.blogspot.se/2009/06/beautiful-race-condition.html)は、これについて私の頭を浮かべるのを助けました。私は数年前に学校でこれについて読んだことを覚えています。 – Gikkman

関連する問題