从多个线程填充地图

时间:2015-12-21 22:14:38

标签: java multithreading guava concurrenthashmap

我有一个ConcurrentHashMap,我从多个线程填充,如下所示:

private static Map<ErrorData, Long> holder = new ConcurrentHashMap<ErrorData, Long>();

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

上述代码中是否存在竞争条件的可能性并且可以跳过更新?另外,如果可以提供更好的性能,我如何在这里使用番石榴AtomicLongMap

我在Java 7上。

4 个答案:

答案 0 :(得分:7)

是的,因为你没有检查包含和原子化,所以有可能发生竞争。

您可以按照以下方式使用AtomicLongMap,这会以原子方式进行检查:

private static final AtomicLongMap<ErrorData> holder = AtomicLongMap.create();

public static void addError(ErrorData error) {
  holder.getAndIncrement(error);
}

如javadoc所述:

  

[T]写入此映射的典型机制是addAndGet(K,long),它为当前与K关联的值添加long。如果某个键尚未与值关联,则其隐含值为零。

  

除非另有说明,否则所有操作均为原子操作。

答案 1 :(得分:4)

如果您使用的是Java 8,则可以利用新的merge方法:

holder.merge(error, 1L, Long::sum);

答案 2 :(得分:4)

A&#39;香草&#39; java 5+解决方案:

public static void addError(final ErrorData errorData) {
    Long previous = holder.putIfAbsent(errorData, 1L);
    // if the error data is already mapped to some value
    if (previous != null) {
        // try to replace the existing value till no update takes place in the meantime
        while (!map.replace(errorData, previous, previous + 1)) {
            previous = map.get(errorData);
        }
    }
}

答案 3 :(得分:2)

在Java 7或更早版本中,您需要使用比较和更新循环:

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

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

考虑:

Thread1: holder.get(error) returns 1
Thread2: holder.get(error) returns 1
Thread1: holder.put(error, 1+1);
Thread2: holder.put(error, 1+1);

要解决此问题,您需要使用原子操作来更新地图。