2012-08-27 10 views
9

Mapintの値で作成し、複数のスレッドでそれらを増やそうとしています。 2つ以上のスレッドが同じキーを増やす可能性があります。java map同時更新

myMap.put(X, myMap.get(X) + 1);

ない場合:ConcurrentHashMap意志を使用して、次のコードは、正しくを動作するかどうか、私は疑問に思う

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove)

:それはあることサイス以来

ConcurrentHashMapドキュメントは、私にとって非常に不明でしたどうすればそのようなことを管理できますか?

+0

をis11759、これは安全 'myMap.merge(X、1、整数で行うことができます。 :sum) ' – shmosel

答えて

9

同時実行マップは、コードのスレッドの安全性には役立ちません。あなたはまだ競合状態を得ることができます:

Thread-1: x = 1, get(x) 
Thread-2: x = 1, get(x) 
Thread-1: put(x + 1) => 2 
Thread-2: put(x + 1) => 2 

2つの増分が発生しましたが、まだ+1しか得られません。コンカレントマップが必要なのは、マップの内容ではなくマップ自体の変更を目的とする場合のみです。 HashMapでさえもthreadsafe for concurrent readsであり、マップがもはや突然変異していないとすれば、

したがって、プリミティブ型のthreadsafeマップの代わりに、その型のためのスレッドセーフラッパーが必要です。 java.util.concurrent.atomicのいずれかか、任意のタイプが必要な場合は、独自のロックされたコンテナーを巻き取ってください。

1

ブロックをsynchronized (myMap) {...}ブロックに配置するだけです。

3

1つのアイデアは、ConcurrentMapとIncrementメソッドを持つAtomicIntegerを組み合わせることです。不要なオブジェクトの生成を回避するために、余分なコードガードと

(より効率的に、感謝@Keppil)
AtomicInteger current = map.putIfAbsent(key, new AtomicInteger(1)); 
int newValue = current == null ? 1 :current.incrementAndGet(); 

か:

AtomicInteger current = map.get(key); 
if (current == null){ 
    current = map.putIfAbsent(key, new AtomicInteger(1)); 
} 
int newValue = current == null ? 1 : current.incrementAndGet(); 
+0

ConcurrentMapを使用する場合、原子整数を使用する点もありません。このため、ConcurrentHashMap.replace(K、V、V)が作成されます。 – jolivier

+0

更新がまれである場合、@dflemstrのようなシンプルな同期も可能です。 AtomicIntegerを正当化するために必要なスループットが不明です。 – Thilo

+1

@ replaceの 'replace'は再試行の対象となりますが、' getAndIncrement'は返されません。 –

0

あなたの現在のコードは、同時にので、これは動作しませんあなたのマップの値を変更します。

複数のスレッドがputの値をマップに追加できる場合は、ConcurrentHashMapのような同時マップを使用し、Integerなどのスレッドセーフでない値を使用する必要があります。 ConcurrentMap.replaceはあなたが望むことをします(またはコードを簡単にするためにAtomicIntegerを使用します)。

あなたのスレッドは、唯一、あなたはAtomicIntegerのようなスレッドセーフ値を格納標準マップを使用することができ、あなたのマップの(キーを変更/追加していない)の値を変更されます。あなたのスレッドは例えばmap.get(key).incrementAndGet()と呼ぶでしょう。

2

ベストプラクティスHashMapとAtomicIntegerを使用できます。 テストコード:

public class HashMapAtomicIntegerTest { 
    public static final int KEY = 10; 

    public static void main(String[] args) { 
     HashMap<Integer, AtomicInteger> concurrentHashMap = new HashMap<Integer, AtomicInteger>(); 
     concurrentHashMap.put(HashMapAtomicIntegerTest.KEY, new AtomicInteger()); 
     List<HashMapAtomicCountThread> threadList = new ArrayList<HashMapAtomicCountThread>(); 
     for (int i = 0; i < 500; i++) { 
      HashMapAtomicCountThread testThread = new HashMapAtomicCountThread(
        concurrentHashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       HashMapAtomicCountThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" 
       + concurrentHashMap.get(HashMapAtomicIntegerTest.KEY)); 
    } 
} 

class HashMapAtomicCountThread extends Thread { 
    HashMap<Integer, AtomicInteger> concurrentHashMap = null; 

    public HashMapAtomicCountThread(
      HashMap<Integer, AtomicInteger> concurrentHashMap) { 
     this.concurrentHashMap = concurrentHashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      concurrentHashMap.get(HashMapAtomicIntegerTest.KEY) 
        .getAndIncrement(); 
     } 
    } 
} 

結果:

結果値は5000000である必要があり、実際にis5000000

かのHashMapと同期が、前者

public class HashMapSynchronizeTest { 

    public static final int KEY = 10; 

    public static void main(String[] args) { 

     HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>(); 
     hashMap.put(KEY, 0); 
     List<HashMapSynchronizeThread> threadList = new ArrayList<HashMapSynchronizeThread>(); 
     for (int i = 0; i < 500; i++) { 
      HashMapSynchronizeThread testThread = new HashMapSynchronizeThread(
        hashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       HashMapSynchronizeThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" + hashMap.get(KEY)); 
    } 
} 

class HashMapSynchronizeThread extends Thread { 
    HashMap<Integer, Integer> hashMap = null; 

    public HashMapSynchronizeThread(
      HashMap<Integer, Integer> hashMap) { 
     this.hashMap = hashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      synchronized (hashMap) { 
       hashMap.put(HashMapSynchronizeTest.KEY, 
         hashMap 
           .get(HashMapSynchronizeTest.KEY) + 1); 
      } 
     } 
    } 
} 
よりもはるかに遅いです

結果:

結果の値は、実際にis5000000

使用のConcurrentHashMapが間違った結果が得られます、5000000でなければなりません。

public class ConcurrentHashMapTest { 

    public static final int KEY = 10; 

    public static void main(String[] args) { 
     ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>(); 
     concurrentHashMap.put(KEY, 0); 
     List<CountThread> threadList = new ArrayList<CountThread>(); 
     for (int i = 0; i < 500; i++) { 
      CountThread testThread = new CountThread(concurrentHashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       CountThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" + concurrentHashMap.get(KEY)); 
    } 
} 

class CountThread extends Thread { 
    ConcurrentHashMap<Integer, Integer> concurrentHashMap = null; 

    public CountThread(ConcurrentHashMap<Integer, Integer> concurrentHashMap) { 
     this.concurrentHashMap = concurrentHashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      concurrentHashMap.put(ConcurrentHashMapTest.KEY, 
        concurrentHashMap.get(ConcurrentHashMapTest.KEY) + 1); 
     } 
    } 
} 

結果:

結果値が5000000であるべきであるが、実際のJava 8で

+0

あなたは@vtmarvinの答えから原則を学ぶことができます – wodong

関連する問題