В многопоточной среде я выполняю операции получения и размещения в реализации ConcurrentHashMap. Однако результаты неожиданны. Пожалуйста, найдите ниже код и вывод.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Test {
private static final List<String> bars = Arrays.asList("1","2","3","4","5","6","7","8","9","10");
private static final String KEY = UUID.randomUUID().toString();
private static ExecutorService executorService = null;
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
executorService = Executors.newFixedThreadPool(2);
Map<String, AtomicInteger> map = new ConcurrentHashMap<>();
performMapOps(map);
executorService.shutdown();
try {
while (!executorService.awaitTermination(1, TimeUnit.SECONDS));
System.out.println(map.get(KEY));
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void performMapOps(Map<String, AtomicInteger> map) {
for (int i = 1; i <= bars.size(); i++) {
executorService.execute(() -> ops(map));
}
}
private static void ops(Map<String, AtomicInteger> map) {
if (!map.containsKey(KEY)) {
AtomicInteger atomicInteger = new AtomicInteger(1);
map.put(KEY, atomicInteger);
} else {
map.get(KEY).set(map.get(KEY).intValue() + 1);
}
}
}
ВЫВОД - всегда должно быть 10, однако это неверно для приведенного выше кода. Пожалуйста, найдите ниже вывод.
10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
9
10
10
Иногда это дает мне значение, отличное от 10. Пожалуйста, помогите понять, почему такое неожиданное поведение и как это исправить?




Ваш код имеет состояние гонки. Два потока могут одновременно решить, что !map.containsKey(KEY) ложно, и присвоить карте разные new AtomicInteger(1).
Два потока могут одновременно решить, что !map.containsKey(KEY) истинно, и они оба могут оценить map.get(KEY) как одно и то же значение, поэтому сохраните одно и то же новое значение с .set(map.get(KEY).intValue() + 1).
Атомарная операция обновления может быть достигнута с помощью Map.computeIfAbsent() с AtomicInteger.incrementAndGet(), чтобы обеспечить последовательное увеличение счетчика без обновления значения другим потоком:
private static void ops(Map<String, AtomicInteger> map) {
map.computeIfAbsent(KEY, k -> new AtomicInteger()).incrementAndGet();
}
В исходном коде не было «условия гонки», а было несколько условий гонки. Придирка: во втором разделе сценарий, когда map.get(KEY) возвращает одно и то же значение, не является проблемой, так как это на самом деле задумано (значение, возвращаемое map.get(KEY), является AtomicInteger). Не говоря уже о том, что это не гарантируется из-за проблемы, описанной в первом разделе, фактическая вторая проблема заключается в том, что intValue() может возвращать одно и то же значение. Возможно даже, что во время выполнения потока происходит несколько обновлений intValue() + 1, и в конечном итоге все они откатываются.
Вам нужно заменить тело операций на все за одну операцию, например
map.computeIfAbsent(KEY, k -> new AtomicInteger()). incrementAndGet().