2017-08-28 48 views
0

根據我的理解,ConcurrentHashMap將允許多個線程在同一散列映射上讀寫(添加/刪除),而不會出現併發散列映射異常。正在使用同步哈希映射的同步塊是否正確?

我有4個線程,每個線程可以更新散列表。我不希望其他線程在當前線程正在更新時在hashmap上寫/更新。

ConcurrentHashMap<String, Integer> playerLoginCounterHashMap = new ConcurrentHashMap<>(); 

    ExecutorService executorService = Executors.newFixedThreadPool(4); 

    for (int i = 0; i < 4; i++) { 

     executorService.submit(new Runnable() { 
      @Override 
      public void run() { 
       synchronized (playerLoginCounterHashMap) { 
        if (playerLoginCounterHashMap.get("testPlayer") == null) { 
         playerLoginCounterHashMap.put("testPlayer", 1); 
        } else { 
         playerLoginCounterHashMap.put("testPlayer", playerLoginCounterHashMap.get("testPlayer").intValue() + 1); 
        } 
       } 
      } 
     }); 
    } 

這是正確的做法嗎?沒有同步塊我得到的值是不正確的。

+0

該代碼爲您生成一個異常? –

+2

您需要使用'putIfAbsent'或'computeIfAbsent'等併發感知API來允許併發。否則,如果您的整個更新過程需要是原子的,那麼唯一的解決方案就是在更新時鎖定並阻止任何其他線程使用映射,此時ConcurrentHashMap不會爲您添加任何值。 –

+0

@DanielPryden putIfAbsent,雖然 –

回答

2

是的,它是正確的(假設這是地圖更新的唯一地方),但效率不高,因爲它同步而不是依賴於地圖固有的非阻塞併發。

您應該使用compute()代替:

playerLoginCounterHashMap.compute(
    "testPlayer", 
    (key, value) -> value == null ? 1 : value + 1); 

或者merge()

playerLoginCounterHashMap.merge(
    "testPlayer", 
    1, 
    Integer::sum); 
1

請注意,存儲每個用戶的長櫃檯的簡單的情況下,它可能是有意義的使用Google Guava AtomicLongMap

final AtomicLongMap<String> loginCounterByPlayerName = AtomicLongMap.create(); 
final ExecutorService executorService = Executors.newFixedThreadPool(4); 
for (int i = 0; i < 4; i++) { 
    executorService.submit(new Runnable() { 
     @Override 
     public void run() { 
      loginCounterByPlayerName.addAndGet("testPlayer", 1); 
     } 
    }); 
} 

唯一不同事情是,計數器從0開始。