Java ConcurrentHashMap 操作的原子性

13

这可能是一个重复的问题,但我在一本关于并发的书中找到了这段代码。据说这是线程安全的:

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都可以读取到currentCount = 1。然后两个线程都可以将映射的值更改为2。请问有人可以解释一下这段代码是否正确吗?

3个回答

11
技巧在于replace(K key, V oldValue, V newValue)为您提供了原子性。从文档中可以看到 (我强调的):

仅在当前映射到给定值时,替换键的条目 ... 执行的操作是原子的。

"原子性"是关键词。在replace中,“检查旧值是否与我们期望的相同,并且只有在它相同时才替换它”作为一个单独的工作块执行,没有其他线程能够插入其中。由实现来决定需要进行任何同步以确保它提供这种原子性。

因此,两个线程都不能从replace函数中看到currentAction == 1。其中一个会将其视为1,因此其对replace的调用将返回true。另一个将看到它为2(因为第一个调用),因此返回false - 并循环回去尝试,这次使用currentAction == 2的新值。

当然,也可能会出现第三个线程在此期间将currentAction更新为3的情况,那么第二个线程将继续尝试,直到它足够幸运,没有人在它前面跳过它。


非常感谢,我现在明白了。 :) - insan-e

6

请问这段代码是否正确?

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

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

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

-1

使用put方法,您也可以替换值。

if (currentCount == null) {
        counts.put(thing, 2);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接