如何使用 ConcurrentHashMap get 和 put 方法保持原子性?

Pus*_*pak 1 java concurrency multithreading java.util.concurrent java-8

在多线程环境中,我正在 ConcurrentHashMap 实现上执行 get 和 put 操作。然而,结果却出乎意料。请找到下面的代码和输出。

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);
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

输出 - 它应该始终是 10,但是对于上面的代码来说并非如此。请找到下面的输出。

10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
9
10
10
Run Code Online (Sandbox Code Playgroud)

有时,它给我的值不是 10。请帮助理解,为什么会出现这种意外行为以及如何解决这个问题?

Dun*_*ncG 6

您的代码存在竞争条件。两个线程可以同时决定!map.containsKey(KEY)错误并将不同的值分配new AtomicInteger(1)给映射。

两个线程可以同时决定这!map.containsKey(KEY)是真的,并且它们都可以计算map.get(KEY)为相同的值,因此使用 存储相同的新值.set(map.get(KEY).intValue() + 1)

Map.computeIfAbsent()更新的原子操作可以通过使用with来实现,AtomicInteger.incrementAndGet()以确保计数器递增一致,而无需另一个线程更新值:

private static void ops(Map<String, AtomicInteger> map) { 
    map.computeIfAbsent(KEY, k -> new AtomicInteger()).incrementAndGet();
}
Run Code Online (Sandbox Code Playgroud)

  • 原始代码没有“竞争条件”,而是多个竞争条件。吹毛求疵:在第二部分中,“map.get(KEY)”返回相同值的场景不是问题,因为这实际上是预期的事情(“map.get(KEY)”返回的值是“AtomicInteger” `)。抛开第一节中描述的问题不保证这一点,实际的第二个问题是 `intValue()` 可能返回相同的值。当线程执行“intValue() + 1”并最终恢复所有更新时,甚至有可能发生多个更新。 (4认同)