我有一个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上。
答案 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);
要解决此问题,您需要使用原子操作来更新地图。