如何从多个线程填充 concurrenthashmap?
How to populate concurrenthashmap from multiple threads?
我有一个从多个线程填充的 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:errorMap.get(错误)returns 1
- 线程 2:errorMap.get(错误)returns 1
- 线程 1:errorMap.put(错误,1+1);
- 线程 2:errorMap.put(错误,1+1);
围绕 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);
我有一个从多个线程填充的 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:errorMap.get(错误)returns 1
- 线程 2:errorMap.get(错误)returns 1
- 线程 1:errorMap.put(错误,1+1);
- 线程 2:errorMap.put(错误,1+1);
围绕 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);