Java ConcurrentHashMap 操作原子性

这可能是一个重复的问题,但我在一本关于并发性的书中发现了这部分代码。这被认为是线程安全的:

ConcurrentHashMap<String, Integer> counts = new ...;

private void countThing(String thing) {
    while (true) {
        Integer currentCount = counts.get(thing);
        if (currentCount == null) {
            if (counts.putIfAbsent(thing, 1) == null)
                break;
        } else if (counts.replace(thing, currentCount, currentCount + 1)) {
            break;
        }
    }
}

从我(并发初学者)的角度来看,线程 t1 和线程 t2 都可以读取 。然后,两个线程都可以将映射的值更改为 2。有人可以解释一下代码是否正常吗?currentCount = 1


答案 1

诀窍是为您提供原子性。从文档(强调我的):replace(K key, V oldValue, V newValue)

仅当当前映射到给定值时才替换键的条目。...该操作以原子方式执行。

关键词是“原子”。在 中,“检查旧值是否是我们期望的,并且仅当它是,替换它”作为单个工作块发生,没有其他线程能够与它交错。实现需要执行的任何同步取决于它,以确保它提供这种原子性。replace

因此,不能两个线程都从函数内部看到。其中一个将看到它为 1,因此它对的调用将返回 true。另一个将看到它为 2(由于第一次调用),从而返回 false — 并循环回去重试,这次使用的新值 。currentAction == 1replacereplacecurrentAction == 2

当然,可能是第三个线程同时将currentAction更新为3,在这种情况下,第二个线程将继续尝试,直到它足够幸运,没有人跳到它前面。


答案 2

有人可以解释一下代码是否正常吗?

除了yshavit的答案之外,您还可以通过使用Java 8中添加的循环来避免编写自己的循环。compute

ConcurrentMap<String, Integer> counts = new ...;

private void countThing(String thing) {
    counts.compute(thing, (k, prev) -> prev == null ? 1 : 1 + prev);
}

推荐