从多个线程填充地图
Populating map from multiple threads
我有一个从多个线程填充的 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);
}
}
上面的代码中是否存在竞争条件的可能性并且它可以跳过更新?另外,如果 Guava AtomicLongMap 可以提供更好的性能,我该如何在这里使用它?
我在Java 7.
如果您使用的是 java 8,则可以利用新的 merge
方法:
holder.merge(error, 1L, Long::sum);
是的,存在竞争的可能性,因为您没有检查包含和放置原子。
您可以按如下方式使用 AtomicLongMap
,它会自动执行此检查:
private static final AtomicLongMap<ErrorData> holder = AtomicLongMap.create();
public static void addError(ErrorData error) {
holder.getAndIncrement(error);
}
如 javadoc 中所述:
[T]he typical mechanism for writing to this map is addAndGet(K, long), which adds a long to the value currently associated with K. If a key has not yet been associated with a value, its implicit value is zero.
和
All operations are atomic unless otherwise noted.
A 'vanilla' 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);
}
}
}
在 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);
要解决此问题,您需要使用原子操作来更新地图。
我有一个从多个线程填充的 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);
}
}
上面的代码中是否存在竞争条件的可能性并且它可以跳过更新?另外,如果 Guava AtomicLongMap 可以提供更好的性能,我该如何在这里使用它?
我在Java 7.
如果您使用的是 java 8,则可以利用新的 merge
方法:
holder.merge(error, 1L, Long::sum);
是的,存在竞争的可能性,因为您没有检查包含和放置原子。
您可以按如下方式使用 AtomicLongMap
,它会自动执行此检查:
private static final AtomicLongMap<ErrorData> holder = AtomicLongMap.create();
public static void addError(ErrorData error) {
holder.getAndIncrement(error);
}
如 javadoc 中所述:
[T]he typical mechanism for writing to this map is addAndGet(K, long), which adds a long to the value currently associated with K. If a key has not yet been associated with a value, its implicit value is zero.
和
All operations are atomic unless otherwise noted.
A 'vanilla' 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);
}
}
}
在 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);
要解决此问题,您需要使用原子操作来更新地图。