我有一个具有多个端点的Counter类。我想保留每个端点的“访问”次数。可以从不同的线程访问端点,因此我决定使用ConcurrentHashMap


这是我的代码,我创建了一个模拟此行为的类:

public class Counter {

    Map<String, Integer> endpoints = new ConcurrentHashMap<>();

    void load(String endpoint) {

        endpoints.put(endpoint, endpoints.get(endpoint) + 1);
    }

    void accessEndpoint(String endpoint, int times, int numberOfThreads) throws InterruptedException {

        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < times; i++) {
            executor.submit(() -> load(endpoint));
        }

        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);

    }

    public static void main(String[] args) throws InterruptedException {

        Counter counter = new Counter();
        counter.endpoints.put("www.google.com", 2);
        counter.accessEndpoint("www.google.com", 100, 10);
        System.out.println(counter.endpoints.get("www.google.com"));
    }
}


输出不一致。

预期:102

实际:95、100、102、66、100

最佳答案

endpoints.put(endpoint, endpoints.get(endpoint) + 1);不是原子操作。
因此,两个线程可以get()具有相同的数字(例如100),而put具有相同的数字(101),从而使两个调用仅算作一个。

使用compute()代替,它是原子的:

endpoints.compute(endpoint, (k,v)-> v+1);


或者如果您想始终从零开始而不进行初始化。

endpoints.merge(endpoint, 1, Integer::sum);

10-07 16:16
查看更多