2012-08-27 42 views
9

我試圖創建一個Mapint值並增加它們由多個線程。兩個或多個線程可能會增加相同的密鑰。java map併發更新

ConcurrentHashMap文檔很我不清楚,因爲它最高審計機關認爲:

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

我不知道如果使用下面的代碼ConcurrentHashMap意志工作正確

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

如果不是,我怎麼能管理這樣的事情?

+0

在Java 8,這可以安全地使用'myMap.merge(X,1,整數做: :總和)'。 – 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 

發生了兩次增量,但您仍然只獲得+1。只有當您打算修改地圖本身而不是其內容時,您才需要併發地圖。即使最簡單的HashMapthreadsafe for concurrent reads,因爲地圖不再變異。

因此,不需要針對基元類型的線程安全映射,您需要該類型的線程安全包裝器。要麼是java.util.concurrent.atomic的東西,要麼是自己鎖定的容器,如果需要任意類型的話。

1

您可以將操作置於synchronized (myMap) {...}塊中。

3

一個想法是將ConcurrentMap和AtomicInteger結合起來,它有一個增量方法。

AtomicInteger current = map.putIfAbsent(key, new AtomicInteger(1)); 
int newValue = current == null ? 1 :current.incrementAndGet(); 

或(更有效,感謝@Keppil)有一個額外的代碼後衛,以避免不必要的對象創建:

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

@jolivier'replace'可以重試,而'getAndIncrement'則不可以。 –

0

您當前的代碼更改地圖的同時值,因此這將無法正常工作。

如果多個線程可以將put值輸入到您的映射中,則必須使用像ConcurrentHashMap這樣的併發映射,並使用非線程安全值,如IntegerConcurrentMap.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); 
      } 
     } 
    } 
} 
慢得多

結果:

結果值應爲5000000其實is5000000

使用ConcurrentHashMap就得到錯誤的結果。

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其實is11759

+0

你可以從@vtmarvin的回答中瞭解原理 – wodong