如何从多个线程填充concurrenthashmap?

时间:2015-06-19 02:31:26

标签: java multithreading thread-safety concurrenthashmap

我有一个ConcurrentHashMap,我从多个线程填充。

private static Map<DataCode, Long> errorMap = new ConcurrentHashMap<DataCode, Long>();

public static void addError(DataCode error) {
    if (errorMap.keySet().contains(error)) {
        errorMap.put(error, errorMap.get(error) + 1);
    } else {
        errorMap.put(error, 1L);
    }
}

我的上述addError方法是从多个线程调用的,它填充了errorMap。我不确定这是否是线程安全的?我在这里做错了什么?

任何解释为什么它可以跳过更新将有助于我更好地理解。

1 个答案:

答案 0 :(得分:2)

这是否安全取决于你的意思。它不会抛出异常或破坏地图,但它可以跳过更新。考虑:

  1. Thread1:errorMap.get(error)返回1
  2. Thread2:errorMap.get(error)返回1
  3. Thread1:errorMap.put(error,1 + 1);
  4. Thread2:errorMap.put(错误,1 + 1);
  5. keySet().contains(error)操作周围存在类似的竞争。要解决此问题,您需要使用原子操作来更新地图。

    在Java 8上,这很简单:

    errorMap.compute(error, oldValue -> oldValue == null ? 1L : oldValue + 1L);
    

    在旧版本的Java上,您需要使用比较和更新循环:

    Long prevValue;
    boolean done;
    do {
      prevValue = errorMap.get(error);
      if (prevValue == null) {
        done = errorMap.putIfAbsent(error, 1L);
      } else {
        done = errorMap.replace(error, prevValue, newValue);
      }
    } while (!done);
    

    使用此代码,如果两个线程竞争可能最终重试其更新,但它们最终会得到正确的值。

    或者,您也可以使用Guava的AtomicLongMap为您提供所有线程安全魔法并获得更高的性能(通过避免所有这些拳击操作等):

    errorAtomicLongMap.incrementAndGet(error);